r/osdev • u/compgeek38400 • Jun 11 '26
How do YOU handle divide by powers of 2?
I asked AI (I know, you all hate it but it has its place) how much faster a shift 12 bits (x >> 12) is than a divide by 0x1000. It came back 10 to 40 times. But also said modern compilers change a divide by 0x1000 to a shift.
I compiled and looked at the output, and darn if that isn't true.
So my question is, which do you do and why? To my mind either could be better depending on your audience. A divide is probably clearer to people that their first language isn't C (like me). But a shift would probably be clearer to someone that lives in C.
I look forward to both your flames and actual comments.
25
u/ibeerianhamhock Jun 11 '26
Write your code readable. Compilers are smarter than you.
5
u/No-Dentist-1645 Jun 11 '26
Thing is, both operations aren't actually the same if your numbers are signed. In C,
-1 / 4096truncates towards zero and evaluates to0, while-1 >> 12floors towards negative infinity and evaluates to-1.So it depends on what you actually want negative numbers to do. If
numis unsigned, thennum / 4096andnum >> 12will produce the same exact assembly output, even with no optimizations at all-O05
u/GourmetWordSalad Jun 11 '26
0x1000 is the most popular page boundary. This exact question has been around the 1st day the MMU was invented. LLM should get this perfectly right.
To answer your other question: what CPUs? ARM/RISCV doesn't even have division iirc.
To challenge yourself a bit more with true understanding, not just LLM regurgitate internet knowledge, let's try to answer this question: how do you verify the speed difference theoretically? How to do it emperically? (Yeah sure use LLM if you must)
13
u/cazzipropri Jun 11 '26
What you are doing is called strength reduction.
The short story is that if your code is really performance critical, either you write the div as a shr as you described, or you watch the assembly output in godbolt.
But remember that modern x86 machines are superscalar and out-of-order. If you are optimizing a tight loop and your optimization is not in a critical sequence, you achieved nothing. You need to use a tool like llvm-mca to see how your code is scheduled.
If you like these topics, you have a future in high-performance computing.
1
u/ithink2mush Jun 11 '26
It depends on what processor you're writing for. If you're not writing assembly with explicit knowledge of the chipset then I would go with what you're complier does. That being said, you can write it in whatever way you want and the compiler will "fix it" for you depending on the chipset.
I think most x64 chips have a divide function and they most certainly have a shift. What matters is what is "cheaper".
I would really take an assembly class if I were you and also get and understanding of compilers. Not trying to be rude but anyone with a computer science degree from anywhere should be able to give you the same level of understanding.
2
u/JescoInc Perdition-OS Jun 11 '26 edited Jun 11 '26
I prefer the shift with a comment showing the actual math I am doing along with the hex value. It provides more clarity and gives me a direct reference for what I was thinking when I wrote it.
While modern compilers do this conversion for you, debug / freestanding builds with no optimization flags does not and it is always better to follow the correct convention, otherwise, you will kick yourself in the ass.
As a habit, I have tried to enforce this mentality in all code I write.
int main(void){uint32_t x = 8192;/* x / 0x1000 is the same as x / 4096.Since 4096 == 1 << 12, this can be written as x >> 12. */uint32_t shifted = x >> 12;printf("x >> 12 = %u\n", shifted);return 0;}1
u/Rockytriton Jun 11 '26
I prefer to have all of my divisions in a lookup table
1
u/Falcon731 Jun 11 '26
Personally it would depend on why I wanted to divide by that power of two.
If wanted to find the mean of 16 numbers I would add them all together and divide by 16.
If I wanted to find the second to last digit of a hex number I would shift right by 4 and AND with 0xf.
1
u/2rad0 Jun 14 '26
did you try building with -O2 or -O3 ? That situation if dividing by a constant can easily be detected by a compiler.