4 billion if statements (2023)
Posted by damethos 9 days ago
Comments
Comment by xnorswap 3 days ago
The best way to save space is to use a Bloom Filter.
If we capture all the even numbers, that would sadly only give us "Definitely not Even" or "Maybe Even".
But for just the cost of doubling our space, we can use two Bloom filters!
So we can construct one bloom filter capturing even numbers, and another bloom filter capturing odd numbers.
Now we have "Definitely not Even" and "Maybe Even" but also "Definitely not Odd" and "Maybe Odd".
In this manner, we can use the "evens" filter to find the odd numbers and the "odds" filter to find the even numbers.
Having done this, we'll be left with just a handful of unlucky numbers that are recorded as both "Maybe even" and "Maybe odd". These will surely be few enough in number that we can special case these in our if/else block.
The filters as a first-pass will save gigabytes of memory!
Comment by gopalv 3 days ago
We can optimize the hash function to make it more space efficient.
Instead of using remainders to locate filter positions, we can use a mersenne prime number mask (like say 31), but in this case I have a feeling the best hash function to use would be to mask with (2^1)-1.
Comment by AlotOfReading 3 days ago
Comment by piersadrian 2 days ago
Comment by alexjurkiewicz 2 days ago
https://gist.github.com/alexjurkiewicz/1abf05f16fd98aabf380c...
Comment by nixpulvis 3 days ago
It's a joke post with some interesting bits and details.
Comment by xnorswap 3 days ago
It is hard to imagine better efficiency than O(1)!
Indeed we could improve it further by performing all evaluations even when we find the answer earlier, ensuring it is a true Constant Time algorithm, safe for use in cryptography.
Comment by nixpulvis 3 days ago
You're saying that the blog's solution is time efficient. Which it is not. Your solution may be O(1) but it is also not efficient. As I'm sure you are aware.
I can tell you a practical solution which is also O(1) and takes up maybe 2 or 3 instructions of program code and no extra memory at all.
`x & 1` or `x % 2 != 0`
This blog post was taking a joke and running with it. And your comment is in that spirit as well, I just wanted to point out that it's by no means time efficient when we have 2s or 1s complement numbers which make this algorithm trivial.
Comment by icambron 3 days ago
Comment by nixpulvis 3 days ago
lol
Comment by kbelder 3 days ago
Comment by henrikschroder 2 days ago
We already know. Everybody knows. That's the joke. There's no need to point out anything.
Comment by Sohcahtoa82 3 days ago
Comment by nixpulvis 2 days ago
Comment by uplifter 2 days ago
And with a little extra work you can shrink the whole table's size in memory by a factor of eight, but I'll leave that as an exercise for the interested reader.
Comment by jiggawatts 2 days ago
"That's good enough, ship it to production. We'll optimise it later."
Comment by mandarax8 2 days ago
Comment by __david__ 1 day ago
Comment by Maxatar 3 days ago
Comment by nixpulvis 2 days ago
Comment by nrhrjrjrjtntbt 2 days ago
Comment by ZeroConcerns 3 days ago
Optimization efforts included increasing the internal buffer size of the StreamWriter used to create the source code: this reduced the runtime to around 6 minutes, as well as running a non-debug build, as it was observed that the poor Visual Studio metrics gathering process was contributing significantly to disk activity as well, but that ultimately didn't matter much. So, ehhm, yes, good job on that I guess?
Comment by afandian 3 days ago
And if you then make the ranges tree-shaped you get logarithmic complexity, which massively cuts down the O(n) of the rather naive chained `if` statements.
Comment by jiggawatts 2 days ago
I haven't found any authoritative source, but I strongly suspect that the .NET bytecode format has 32-bit limits all over the place. Maaaybe you could break up the code into functions less than 1 GB in size and then chain them together.
Comment by opticfluorine 3 days ago
Comment by tkapin 3 days ago
Comment by xnorswap 2 days ago
So you have to make sure to compile only in release mode just to get to 16 bits.
Comment by szundi 3 days ago
Comment by mft_ 3 days ago
Comment by thih9 3 days ago
[1]: https://aphyr.com/posts/342-typing-the-technical-interview
[2]: https://www.richard-towers.com/2023/03/11/typescripting-the-...
Comment by waisbrot 3 days ago
> “Can I use any language?” > > “Sure.” > > Move quickly, before he realizes his mistake.
Comment by thih9 2 days ago
I’m almost serious, the only time I saw haskell in production was after a similar scenario.
Comment by kayge 3 days ago
Wow he really lucked out... On his way to perfecting a fully functioning and performant Even/Odd Detector, he stumbled upon a fully functioning and performant Coin Flip Simulator!
Comment by thedougd 3 days ago
I started designing my modules, a ROM, a register with a ROM pointer, etc, etc, writing the Verilog and working out the clock sync between modules. Then I got 'lazy' and wrote a trie tree like implementation in Java, and have it spit out the whole tree in Verilog. It worked and just one clock cycle after the last letter my number would output. Fastest in the class! Also the most number of gates in the class. Surprised I got a 90 grade given I didn't use any of the advanced ASIC design the class taught. The TA didn't know what the hell they were looking at.
Comment by weli 3 days ago
Comment by retrac 2 days ago
It's a common pitfall for those learning hardware description languages like Verilog, when they think about them like programming languages. If you go "if (calc) res <= a * b;" If res is 32 bits wide then you have instantiated a 32 bit fast multiplier circuit dedicated just to that one operation. This is often not what was intended.
Despite how leaning on the analogy too closely can mislead in that way, the analogy between hardware and software is not a shallow one. A combinatorial circuit is akin to the pure function of functional programming. Anything that can be described as a pure function working on fixed integers or floating point or other discrete data types, can be transformed into a combinatorial circuit. And there are algorithms to do so automatically and often with reasonable efficiency.
Free software synthesis has come a long way in recent years, by the way. There's even several hobbyist projects that can take VHDL or Verilog and produce layouts using TTL chips or even discrete transistor logic with automatic circuit board layout. You can now compile your code directly to circuit board copper masks and a part list.
Comment by wiz21c 3 days ago
So if you're concerned with privacy issues, you can run the assembly version proposed in the article locally and be well within the same order of performance.
Let's thank the author of the article for providing a decent alternative to Google.
ah, but the license is not that good we can't reproduce his code.
Comment by brap 3 days ago
I think I just experienced a segfault
Comment by sethaurus 3 days ago
Comment by pksebben 3 days ago
Comment by iberator 3 days ago
Comment by classified 3 days ago
Did you want to test the LLM's grammatical comprehension?
Comment by wiz21c 3 days ago
Comment by moffkalast 3 days ago
Comment by kspacewalk2 2 days ago
Comment by blauditore 3 days ago
Or even simpler: If it's 0, return "even". If not, do a recursive call to n-1, if that equals "even", return "odd", otherwise return "even".
But the best way is probably to just use a library. Yes, 500MB of additional dependencies, but then it's a one-liner.
Comment by majkinetor 3 days ago
Comment by IncreasePosts 3 days ago
Comment by hybridtupel 2 days ago
Comment by rdiddly 3 days ago
Comment by chihuahua 2 days ago
Comment by colejohnson66 2 days ago
Comment by blauditore 1 day ago
Comment by bobbylarrybobby 2 days ago
Comment by layer8 3 days ago
Comment by xg15 3 days ago
for i in range(2*8):
if i % 2 == 0:
No comment...Comment by _ache_ 3 days ago
for i in range(0, 2**8, 2):
print(" if (number == "+str(i)+")")
print(" printf(\"even\\n\");")
print(" if (number == "+str(i + 1)+")")
print(" printf(\"odd\\n\");")
or for i in range(0, 2**8, 2):
print(f""" if (number == {i})
puts("even");
if (number == {i + 1})
puts("odd");""")Comment by NetMageSCW 3 days ago
Comment by PurpleRamen 3 days ago
Comment by travisgriggs 3 days ago
Comment by PurpleRamen 3 days ago
But thinking about, we probably have to use some more microservices, we can't put all that burden on the requester. So a dedicated service for compiling and executing in sandboxes would be necessary. Also, some local load balancers to control the flow and filter out the useless answers. So I'm not an expert on that devops-magic, but I guess this means ~12.5 billion pods fast enough result. Do Amazon or Google offer planetary scale for services?
Comment by cowsandmilk 3 days ago
Comment by catigula 3 days ago
even, odd = "even", "odd"
for i in range(2\*32):
print(f' if (number == {i}) puts("{even}");')
even, odd = odd, even
As usual, a non-marginally superior mind to commentators.Comment by layer8 3 days ago
Comment by _kst_ 2 days ago
// Check whether a number is odd or even.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
static bool is_odd_or_even(unsigned long num) {
return true;
}
int main(int argc, char **argv) {
const unsigned long num = strtoul(argv[1], NULL, 10);
printf("%lu is %s odd or even\n",
num,
is_odd_or_even(num) ? "is" : "is not");
}Comment by niccl 2 days ago
Comment by mring33621 3 days ago
Comment by orzig 3 days ago
Comment by franciscop 3 days ago
Comment by 867-5309 3 days ago
Comment by travisgriggs 3 days ago
I was gonna comment “this is what I really like to see on HN”. Then I saw the date and was sad that we’re having to dip into the history box to find fun/interesting articles more often of late it seems.
Anyone else interested in a temporary moratorium on all things LLM? We could have GPT-free-Wednesday, or something like that :)
Comment by virgilp 3 days ago
Comment by bigstrat2003 2 days ago
I would be interested in a permanent moratorium, personally. There's no interesting content to be had in the various LLM articles that litter HN these days. Or failing a topic ban, at least give a way to filter it for those of us who are sick of hearing about AI hype.
Comment by LPisGood 3 days ago
We don’t _have_ to. You could start a blog and display the indomitable human spirit.
Comment by billforsternz 2 days ago
/* Copyright 2023. All unauthorized distribution of this source code
will be persecuted to the fullest extent of the law*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
int main(int argc, char* argv[])
{
uint8_t number = argc>1 ? argv[1][strlen(argv[1])-1]-'0' : printf("Usage: odd-or-even number\n");
if (number == 0)
printf("even\n");
if (number == 1)
printf("odd\n");
if (number == 2)
printf("even\n");
if (number == 3)
printf("odd\n");
if (number == 4)
printf("even\n");
if (number == 5)
printf("odd\n");
if (number == 6)
printf("even\n");
if (number == 7)
printf("odd\n");
if (number == 8)
printf("even\n");
if (number == 9)
printf("odd\n");
if (number == 10)
printf("even\n");
}
This way it basically works. It's a shame that it doesn't call out a non numeric argument but that's about the only problem. It relies on a trick, printf() returns the number of characters printed, so the error message string needs to be longer than 10.Comment by pcthrowaway 2 days ago
Or is the performance considered worse because it becomes O(n) (where n < MAX_UINT) vs. constant time ( O(MAX_UINT) )
Comment by billforsternz 2 days ago
Comment by LegionMammal978 2 days ago
uint8_t number = (argc<2||sscanf(*++argv,"%*[0123456789]%n",&argc)||argc--[*argv]?printf("bad\n"):argc[*argv])-'0'; // No problems here
Though with either function you may run into issues if the OS allows arguments longer than INT_MAX. To be defensive, you could use "%32767s" or "%*32767[0123456789]%n" instead, at the cost of failing inputs longer than 32KiB.Comment by billforsternz 22 hours ago
Comment by oneeyedpigeon 3 days ago
return odd_or_evenness[n];
works for me, alongside a pretty big array.Comment by layer8 3 days ago
Comment by projektfu 3 days ago
Comment by Zambyte 3 days ago
const odd_or_evenness = comptime blk: {
var buf: [16]bool = undefined;
var is_even = false;
for (&buf) |*b| {
b.* = is_even;
is_even = !is_even;
}
break :blk buf;
};
This looks like a promising strategy.Comment by enopod_ 3 days ago
Comment by jagged-chisel 3 days ago
switch (v) {
case: 0,2,4,8,…:
return EVEN;
case: 1,3,5,7,…:
return ODD;
default:
return IDK;
}
Slightly less code to generate.Comment by willguest 3 days ago
processing the whole number is absurd
Comment by pkaeding 3 days ago
You know, to save the performance cost of processing the input as a string, and chomping off all but the last character.
Comment by mgaunard 3 days ago
All you need is the final binary digit, which incidentally is the most optimal codegen, `v & 1`.
Comment by 71bw 3 days ago
Comment by fainpul 2 days ago
Save space.
def even_flip_flop(number):
even = True
for _ in range(number):
even = not even
return even
Ditto. Sure, this overflows the stack, but you look cool doing it. def even_recursive(number):
return True if number == 0 else not even_recursive(number - 1)
Save time. Just buy more RAM. table = [True, False] * 1000 # adjust to your needs
def even_lookup(number):
return table[number]Comment by bobbylarrybobby 2 days ago
Comment by thewisenerd 3 days ago
https://news.ycombinator.com/item?id=38790597
4B If Statements (469 comments)
Comment by unwind 3 days ago
Comment by lubujackson 3 days ago
isEven is a performant, hand-compiled evenness checker for any 32 bit integer. A single file import that does one job and one job only!
Comment by not-so-darkstar 3 days ago
Comment by biglost 2 days ago
Comment by isoprophlex 3 days ago
Thanks for making me doubt myself & googling who that guy who made python was again, because surely "van der Gussom" isn't a normal Dutch name. Well played.
Comment by mcny 3 days ago
Comment by SiempreViernes 3 days ago
Amazing!
Comment by tanseydavid 3 days ago
Comment by rossdavidh 3 days ago
Comment by robotguy 2 days ago
"Is it odd or Even?"
"YES"
Comment by Ensorceled 2 days ago
I tracked it down to a folder with thousands of C++ files called things like uint_to_int.cc and inch_to_cm.cc and cm_to_m.cc. Basically the developer in charge of writing the conversion library took our typed units library and autogenerated a C++ file for every possible conversion the application might need to make.
Every time we added a new typed unit it would create another couple of dozen files to be compiled.
Comment by avandecreme 2 days ago
There was a function to detect a key press which would return a number identifying the pressed key.
I needed to map that number to the letter printed on the key to print it on the screen. I don't remember whether there was no hashmap data structure or I just didn't know about it, but I implemented it with a serie of if.
The problem with that solution is that while mapping A was fast, Z was very slow because it was at the end of the list. That is how I discovered divide and conquer/ dichotomy with if branches.
Comment by dorianmariecom 2 days ago
File.write("check.rb", (["if i == 0\n puts :even"] + (1..1_000_000).map { |i| "elsif i == #{i}\n puts :#{i % 2 == 0 ? "even" : "odd"}" } + ["end\n"]).join("\n"))
and added at the top i = ARGV.first.to_i
but i'm getting SIGILL fish: Job 1, 'ruby check.rb 0' terminated by signal SIGILL (Illegal instruction)Comment by sedatk 2 days ago
Comment by KeplerBoy 3 days ago
Comment by bspammer 3 days ago
> Lets compile the code, disabling optimizations with /Od to make sure that the pesky compiler doesn’t interfere with our algorithm
Comment by bluGill 3 days ago
Comment by encom 3 days ago
Comment by encom 3 days ago
I'm in too deep now, so I'll let it run while I'm at work.
Comment by KeplerBoy 2 days ago
Comment by encom 2 days ago
I don't know enough about compilers to answer why this doesn't get optimised down to something tiny, or why it took so long. I'm not sure what we've learned tonight, but there you go.
Comment by oneeyedpigeon 3 days ago
And that weird flag is because it's a windows compiler: cl.exe, not gcc.
Comment by rplnt 2 days ago
Comment by ku1ik 19 hours ago
Comment by whynotmaybe 3 days ago
Am I lost? Aren't the compiler/linker responsible for fast code, not the language itself?
Comment by bluGill 3 days ago
Most of the difference in speed is the optimizer/linker. Assuming a fair competition the difference between Ada, C, C++, D, Fortran, Rust, Zig (and many others I can't think of) is very rarely even 1% in any real world situation. Of course it isn't hard add pessimization to make any language lose, sometimes accidentally, so fair competitions are very hard to find/make.
Then again, the article was clearly sarcastic.
Comment by mbivert 3 days ago
given the article, it's fair to assume the author was joking around
that being said, the way the language is used and its ecosystem do contribute to the executable's efficiency. yet, given C's frugality, or the proximity between its instructions and the executed ones, it's not unfair to say that "C is fast"
Comment by rcxdude 3 days ago
Comment by reedf1 3 days ago
Comment by croes 3 days ago
Comment by philippta 3 days ago
If anyone knows what’s actually going on, please do tell.
Comment by ricardo81 3 days ago
Comment by tomtomtom777 3 days ago
The numbers match quite nicely. 40gb program size minus 32gb RAM is 8gb, divided by 800mb/s makes 10 seconds.
Comment by hellzbellz123 3 days ago
Comment by bmacho 2 days ago
Comment by zkmon 2 days ago
https://github.com/toji/gl-matrix/blob/master/dist/gl-matrix...
Comment by SatvikBeri 3 days ago
Ok but if you do want to play with writing binary code manually I recommend Casey Muratori's performance course
Comment by mgaunard 3 days ago
The technique actually has a lot of practical applications, so it's useful to have a C++ library that helps you with generating amd64 machine code.
Comment by 1a527dd5 3 days ago
Comment by runtimepanic 3 days ago
Comment by DeathArrow 3 days ago
Comment by klaff 3 days ago
Comment by DeathArrow 3 days ago
Comment by quux 2 days ago
Comment by riwsky 3 days ago
Comment by layer8 3 days ago
Moreover, interviewers will be smitten by the hand-optimized assembly code.
Comment by almosthere 2 days ago
Comment by tigranbs 3 days ago
Comment by tomaskafka 3 days ago
Comment by Sulf1re 2 days ago
Comment by sfink 2 days ago
printf("%d is %s\n", n, last_binary_bit(n) == 0 ? "even" : "odd");
and the rest is trivial: int last_binary_bit(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
if (n == 2) return 0;
...
}
Come to think of it, with a little fancy math you could divide and conquer: int last_binary_bit(int n) {
// Handle the easy cases.
if (n == 0) return 0;
if (n == 1) return 1;
// Number may be large. Divide and conquer. It doesn't matter where we split it,
// so use a randomized algorithm because those are fast.
for (;;) {
int r = random();
if (r < n) {
// Smaller numbers are easier.
int smaller1 = r;
int smaller2 = n - 4;
int bit1 = last_binary_bit(smaller1);
int bit2 = last_binary_bit(smaller2);
// Fancy math: even + even is even, even + odd is odd, etc.
if (bit1 == 0 && bit2 == 0) return 0;
if (bit1 == 0 && bit2 == 1) return 1;
if (bit1 == 1 && bit2 == 0) return 1;
if (bit1 == 1 && bit2 == 1) return 0;
}
}
}Comment by nonethewiser 3 days ago
Comment by taylorallred 2 days ago
not eax
and eax, 1Comment by wvbdmp 3 days ago
Comment by d--b 3 days ago
Comment by wiz21c 3 days ago
Comment by actionfromafar 3 days ago
Comment by roguecoder 2 days ago
Comment by asgs 2 days ago
Comment by shevy-java 3 days ago
Comment by ks2048 3 days ago
Comment by Exuma 3 days ago
Comment by pcthrowaway 2 days ago
Lol
Comment by tornadofart 3 days ago
Comment by nmilo 3 days ago
Comment by ajsnigrutin 3 days ago
Many gigabytes saved!
/s
Comment by ITniggah 2 days ago
Comment by d-lisp 3 days ago
else
Comment by imtringued 3 days ago
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage: %s <string>\n", argv[0]);
return 1;
}
char *s = argv[1];
int i;
/* find the end of the string */
for (i = 0; s[i] != '\0'; ++i)
;
/* make sure the string wasn't empty */
if (i == 0) {
fprintf(stderr, "Error: empty string\n");
return 1;
}
/* last character is at s[i - 1] */
char d = s[i - 1];
if (d == '0')
printf("even\n");
if (d == '1')
printf("odd\n");
if (d == '2')
printf("even\n");
if (d == '3')
printf("odd\n");
if (d == '4')
printf("even\n");
if (d == '5')
printf("odd\n");
if (d == '6')
printf("even\n");
if (d == '7')
printf("odd\n");
if (d == '8')
printf("even\n");
if (d == '9')
printf("odd\n");
return 0;
}
gcc -std=c11 -Wall -Wextra -O2 -o check_digit check_digit.c./check_digit 999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
Comment by Tistron 3 days ago
if (d & 1)
printf("odd\n");
else
printf("even\n")Comment by d-lisp 3 days ago
#define _(e) { e;};
#define r(e) _(return e)
#define I(b, e) _(if (b) r(e));
#define W(e) _(while (1) _(e));
int main(int c, char **v) {
_(I(c != 2, -1) _(c = 0) W(I(!v[1][c++], v[1][c - 2] & 1)))
}Comment by tetris11 3 days ago
number="$1"
if [[ "$number" =~ "^(2|4|6|8|10|12|14|16|18|20)$" ]]; then
echo even
elif [[ "$number" =~ "^(1|3|5|7|9|11|13|15|17|19)$" ]]; then
echo odd
else
echo Nan
fi
A bit limited, but you can scale it upComment by teo_zero 2 days ago
case "$1" in
*0|*2|*4|*6|*8) echo even;;
*) echo odd;;
esac
If $1 had a trailing non-digit, or was empty, that would indeed be an odd situation!Comment by wiz21c 3 days ago
Comment by actionfromafar 3 days ago