Do you know how much your computer can do in a second?
computers-are-fast.github.io
computers-are-fast.github.io
In my experience, this is something that a lot of developers don't really comprehend. Many of them will have some idea about theoretical time complexity, but then see nothing wrong with what should be a very trivial operation taking several seconds of CPU time on a modern computer. One of the things I like to do is tell them that such a period of time corresponds to several billion instructions, and then ask them to justify what it is about that operation that needs that amount of instructions. Another thing is to show them some demoscene productions.
I got a few of these questions wrong because I don't use Python, but I could probably say with reasonable confidence how fast these operations could be.
Related articles:
https://en.wikipedia.org/wiki/Wirth%27s_law
http://hallicino.hubpages.com/hub/_86_Mac_Plus_Vs_07_AMD_Dua... (I know title is a BuzzFeed-ism, but this article came from before that era.)
If I were writing some python, which means I've already made that trade off, you wouldn't much like my answer if you started asking me such questions. Which is a shame because I'd probably rather have quite a lot of respect for the same point if we were working lower down the stack.
Amusingly, in the first example, the compiler's static analysis concludes that the entire for loop can be omitted. I have no idea how they came up with that precise number...
def f(NUMBER):
for _ in xrange(NUMBER):
pass
runs one tenth as fast! Is it due to the xrange construct? I'm sure that there must be a saner way to make a loop in Python that doesn't bloat to ten times as many cycles as in C. . .You can get it back close to native speed by adding a type annotation and compiling the python file with Cython: http://docs.cython.org/src/userguide/language_basics.html#in...
def f(NUMBER):
cdef int i
for i in range(NUMBER):
passDidn't find the original talk but found this: https://wiki.python.org/moin/PythonSpeed/PerformanceTips#Loo...
% python -mtimeit '[x for x in xrange(1000)]'
10000 loops, best of 3: 70.2 usec per loop
% python -mtimeit 'for x in xrange(1000): pass'
10000 loops, best of 3: 29 usec per loop
% python -mtimeit '[x for x in xrange(100000)]'
100 loops, best of 3: 7.03 msec per loop
% python -mtimeit 'for x in xrange(100000): pass'
100 loops, best of 3: 2.5 msec per loopI think the only reason they haven't is the legacy C interface; it's a shame they didn't do it for Python 3, but I guess it could have delayed conversion of c libraries even more.
Though that still wasn't as fast as gcc on my system (core i5 3.4ghz; using 1e8 rounds):
* cpython 2.7.4 - 1.131s
* pypy 2.6.0 - 0.090s
* gcc w/ "-O2" - 0.050s
I'd say JIT is neither about compilation nor interpretation, but about optimization.
As a hobby I like to hack 6502 assembly language on original hardware, I have Beebs, Ataris, VIC20 and C=64. 2Mhz is the limit. It is amazing to modern eyes how much computation on these systems you can get done once you strip away all the layers of abstraction and indirection modern systems are saddled with.
I like your Mac Plus article; that reminds us that the user experience has not got necessarily got better with all this processing power.
I own a ASUS gaming laptop, and I got astonished by how badly newer games run, even games with graphics that are like of 20 years ago (example: I really like business simulation games, some use isometric graphics with some extremely low poly and untextured models for the walls and floors and some objects, like plants and tables) run slow on my machine.
Even some 2D games run badly, Axiom Verge had several slowdowns on my machine, Luftrausers is virtually unplayable (when there is heavy firefighting on the screen, the game starts to lag severely, things start to teleport around the screen), Wasteland 2 runs like crap and bugs out like crazy (transparent walls, grass growing on random places, people disappearing).
Even games from legendary companies in the technical sense are not doing well, for example I tried to run the new Wolfenstein game on my machine, even at the lowest settings possible the thing go at 3 FPS.
Meanwhile I can play emulated Wii games (Xenoblade for example) with 60 FPS, Stalker series (that in my opinion have amazing graphics) I could even place some mods to make it prettier without losing too much FPS, games using Source engine can confortably run with all the bells and whistles, and so on...
But try to play a new game, even a 2D one, say, Pillars of Eternity. The thing manages to lag for no reason.
I noticed one reason for this is the abuse of Garbage Collectors, many of the games I had problems with has Garbage Collectors, and I see the memory swinging all over the place (a particularly bad one: Kerbal Space Program, it loads everything on launch, then unloads things, then load again as needed, when I launch it, it hits 3gb of memory, then as I use it slowly decrease, then it starts increasing again for no reason, I assume due to memory leaks, and crashes), I even started to play games with the windows task manager on the second monitor, with apps ordered by memory use, this way I know when they will run out of memory and crash (thus I can save first).
And then, there are the devs attitude toward optimization, I finished two days ago some ARM NEON code for my iPhone project (I am doing some freelance iOS coding now), I decided to discuss it on IRC, and some devs got pissed off, they tried to convince me that Apple native APIs were better, and that I was doing my design all wrong, and that instead of using a single ASM function that do all the operations I need inside a single loop, I should have used a collection of Apple APIs that would involve 4 different objects layered to the same thing...
When I explained that my original code (that was using OpenCV), was too slow according to the profiler (it was using 60% of the CPU time), they replied that I was "optmizing the wrong place".
Also when I mention I plan to make it work on iPod 5 and iPhone 4, people counter-argue that I should just ignore those and make it run on iPhone 6 only, because that is the proper solution for not having enough CPU (instead of getting the CPU manual and doing decent code).
Testing in a lot of basic 2D engines also focuses on low performance, e.g. for an RPG where you'll have a dozen sprites moving around, instead of say 500 ships/weapon shots in a shmup.
Incidentally, this is also why you may be able to hear audible sounds from a computer when it's idle or running some particular process - the wakeups/sleeps are happening at a frequency in the hearing range, and the components like capacitors and coils can act as tiny speakers.
Your choice boils down to the classic "message passing" vs. "shared memory" (think MPI vs. OpenMP) architectural choice. What the optimal solution is depends on the specific application, as well as how far you want to scale it.
Is it efficient if the product is buggy and insecure because it's inherently more difficult to program in assembly compared to Python or Java?
Is it efficient if the product is not extensible and inflexible because every engineering decision went towards efficiency, which is inherently opposed to flexibility? (Early binding is inherently more efficient than late binding for all the same reasons it is inherently less flexible.)
Or do you insist on defining "efficiency" solely to mean "machine efficiency" instead of thinking like an engineer and understanding the concept of trade-offs where no decision is inherently worse than any other?
Is it efficient to use thousands of kWh in electricity rather than just hundreds?
As always, the answer obviously depends on your problem.
In this context, it's as if they think that, well, "Every cycle's sacred/Every cycle's great/If a cycle's wasted/God gets quite irate" or similar. It's quite annoying to listen to the more extreme end of this spectrum rattle on about how horrible it is that people no longer use self-modifying code with overlapping opcodes and instruction scheduling which optimizes the drum seek time...
It really seems that many devs are allergic to the word "optimisation" and lash out when they see it. No one says you have to write your code in assembly folks, but do run it in a profiles and make sure it's now slow.
Problems start when everybody too perfectionist or everybody is too quick and dirty.
My way leaves room for "OK, this is crap. It isn't worth making a good version of. At least we didn't waste too much time."
It's amazing how somethings are extremely inefficient, and even worse, nobody questions themselves why this is like this
Most of 'this is taking too long' is because of IO, still, sometimes it's done in a very inefficient way as well
Having moved to very fast SSDs and ramdrives in some cases, it amazes me how many applications can't take advantage of them. It's terrible when you have an app that appears to be IO bound on disk and move it to fast drives only to get a 2x speedup, while even a single cpu core isn't maxed out.
It is a waste of money, I rarely see 100% CPU use, the few times I saw 100% CPU use was demoscene stuff, or some russian compression programs (that can do amazing stuff, like compress 60gb downloads into 20gb, but take 2 hours at 100% CPU to decompress).
It is evne worse that some games that theoretically would need all the CPU (games that has lots of entitites, or are physics or AI intensive), instead just have thousands of cache misses while they loop through their entities in the memory, and the CPU gets used like 20%.
Or they are single-core, even if made recently.
I fixed that to use non-blocking pipes and it was much better.
I wrote a non-trivial stock trading app in C#, and for sake of this conversation I added some loop counting. it analyses about 1.5million ticks per-second (against trade rules). This is a single thread running on a 3.2ghz core i5, no I/O.
If a computer is too slow nobody will use it; if it's only irritatingly slow, some will use it and; if it's slow but still bearable enough, everybody grumbled but still uses it. Development costs and building programs with higher level languages and using big, high-level frameworks saves in development but increases the cost of using the system. The result is that some majority of programs will always be optimized only to the point where they are barely usable.
Similarly, when traffic is always as congested as the people on roads can bear: those will drop out who really hate congestion or prefer to do their trip at another trip but they will be replaced by people who are willing to pay the time tax again. When space is freed on the highway or arterial road, some people will figure out it's worth the trouble and head out.
This means you can't make the user experience faster by getting faster computers and you can't build more roads and lanes to get rid of congestion.
But there's more.
Because a large mass of congested traffic is more difficult to separate and channel into exits congestion on large roads tends to be worse. If you have a village main street with one lane each way on a road, and that road gets traffic congestion that's much less total congestion, area-wise, and it will clear up pretty quickly once the head of traffic is able to exit somewhere. Even so with a grid of 1+1 lane streets, like in a small town. Because the small road gets quickly congested it will only swallow a limited number of cars which will keep a bound on the maximum amount of congested area. Not so with a 10+10 lane highway.
Similarly to computer programs. Slow programs in the early times when everything was simpler could be made faster with sufficient effort. The programs might still struggle in some parts but you could at least invest in the effort to streamline the core operations and make the worst things fast enough. In contrast, consider an enterprise Java program that depends on dozens or hundreds of libraries that amount to a running image of the size of half-a to a whole gigabyte. It takes ten seconds to start and opening each window takes seconds because there's slack everywhere. Everything runs top of something else and even simple operations involve running mundate pieces of code for seconds in wallclock time. Because the computer that's fast enough to run the program in the first place also has the computing power to run a lot of crap in a similar manner. So the amount of crap just increases as the computing power increases, and getting rid of that crap becomes an unsurmountable task. So it becomes close to impossible to make such a big program run fast because you would have to shrink everything between the application and the hardware in half or in one tenth to get some sort of speed. Coincidentally, old computer systems often had faster user interface latency and responded to user inputs in a very timely manner. They could do the cheap things really fast even if the total lack of computer power still made the programs spend seconds on the heavy processing.
I think it would be lovely in these multicore days if we could dedicate a core to a process and have as close to 0 task switching as possible on that core.
Btw: this was just a quick google search, I've never done this. But I'm pretty sure I've heard Martin Thompson discuss it.
In my experience developers who over-optimize, or who optimize with disgusting hacks, are a vastly bigger problem than developers who don't optimize at all.
There are a vast number of problems where a several second wait for the user is almost irrelevant compared to the damage done to the code base by optimizing those few seconds away.
I'm sorry but I have to disagree completely...
I'm currently rewriting the node-postgres module from scratch and my implementation is between 3 and >1000 times faster than the original (this is not even a joke). And you know why? Because the original actually contains those hacks you're talking about and not the other way around.
And that's where your actual noticable inefficiencies far too often stem from: Inexperienced developers who don't know how to do it the easy way - inefficiencies due to huge workarounds that have hidden costs.
E.g.: Not using builtin methods but writing buggy workarounds. Or even very basic stuff like: Using expensive functions within loops instead of hoisting those outside of it.
I wouldn't even call those "optimizations".
No. My opinion is that if it's slow you need to check to see if it's actually a problem for the users first before optimizing. Unless...
>And that's where your actual noticable inefficiencies far too often stem from: Inexperienced developers who don't know how to do it the easy way
I already said this below:
>Cleaning up dirty code that incidentally causes performance improvements is fine
Not only. Think of all these seconds of computing time, translated into watts, translated into coal or whatever pollution from electricity production, multiplied by the number of users your program has.
I think programmers should also consider the environment, even if it'd have more impact if everyone started by just not owning a car.
Appreciate the sentiment but it's not going to achieve anything. "Tut-tut" attitude is a lot less efficient than simply showing people why it's worth optimizing for their own sake. If it's worth optimizing at all, that is - GP's point is that it's not always worth it. Optimization has a cost. Development cost and maintenance cost. What's the point of making a program faster if nobody can ever really work on that piece of the code again?
By the way, the hour the programmer stays in the office overtime to optimize that code, using computers and lights and other machines, probably damages the environment more than your example before...
What if we're talking about the file-copy API in some widely distributed operating system? That's going to be multiplied by millions and millions of people, and therefore, being an obtuse devil's advocate, i argue that indeed it would have an appreciable impact on the environment.
If, however, you're talking about some relatively infrequently used GUI element in an application which has merely a million users, then okay, indeed, go home and don't waste another hour of lights and computers at your office :).
The kind of unnecessary optimizations I think about as being undesirable are when you spend a week's worth of engineering effort and go through a potentially risky data migration just in order to improve the performance of a command-line production environment management tool that is run once per few months, that only takes a few minutes to run anyway, and whose data is cached server-side by the application running it anyway.
Actual example from my software engineering career.
But for library code, that potentially thousands or even millions of systems in production worldwide are going to be using? Hell yes. That is worth the time to improve. And kudos to you on improving Node postgres performance. That's awesome.
This has a real cost associated with it, servers, datacentre space, power and cooling, system administrators to look after it all, don't come for free. Not just money, the environment too.
I still doubt that the CPU/power/cooling cost of having systems infested with hacky enterprise garbage is its biggest drawback.
That crap also causes expensive bugs and huge maintenance costs (e.g. relatively simple programs that require a team of 12 programmers a month to implement the simplest feature).
[1] http://www.ibm.com/software/htp/cics/ [2] https://en.wikipedia.org/wiki/CICS - yes, "Initial release: 1968; 47 years ago"
It's the other kinds of enterprise systems that are crap and deserve no respect. Intranet apps in big corporations, the BS systems that always mess up your bills in services companies, slow as molasses web portals for big enterprises and organisations etc, ERP and CRM apps...
This is evidence of how hard it is to do everything right, especially if you have more than one axis along which you are trying to be right.
Nonetheless, warning bells go off inside my head whenever I hear that opinion expressed without the caveat "if it isn't a problem for the user, leave it the fuck alone".
>You shouldn't have to add disgusting hacks to complete a trivial operation in less than a second.
If a trivial operation takes more than a second that's almost always indicative of a bigger underlying problem of crappy code.
Cleaning up dirty code that incidentally causes performance improvements is fine, but changing code specifically to make performance improvements without improving its overall quality leads to a downward spiral of shit.
I have noticed that the size of L2 caches hasn't increased anywhere near the same rate as other memory capacity.
http://www.slideshare.net/EmanWebDev/pitfalls-of-object-orie...
Many of these questions are heavily dependent on the OS you're running and the filesystem used, and of course the heavy emphasis on Python makes it hard to make good guesses if you've never written a significant amount of it. I mean, I have no idea how much attention was paid to the development of Python's JSON parser; it's trivial to write a low-quality parser using regexes for scanning, OTOH it could be a C plugin with a high-quality scanner, and I could reasonably expect 1000x differences in performance.
Interpreted languages tend to have less predictable performance profiles because there can be a large variance in the amount of attention paid to different idioms, and some higher-level constructs can be much more expensive than a simple reading suggests. Higher level languages also usually make elegant but incredibly inefficient implementations much more likely.
Yajl (Yet Another JSON Library) seems to go about 10x faster than the standard library json
But yeah, some of the questions were poorly chosen. For example, on my machine, using gcc -O2 as the author specifies, the first program always executes instantly, since gcc optmizes that loop to nothing. That leads me to believe he may be running a mac (IIRC those come with absurdly out of date gcc versions as a result of the GPLv3 dispute?) or something else fishy is going on.
One interesting thing to note is that the memory access latency on his machine is almost an order of magnitude faster than you might expect based on "Latency Numbers Every Programmer Should Know" (https://gist.github.com/jboner/2841832) - and that's not a coincidence; those 100ns have always been a bit conservative, and the latency number has slightly improved in the past two decades.
Other readers, check it out for yourself with
gcc -g sum.c ; echo 'disas main' | gdb ./a.out
gcc -g -O2 sum.c ; echo 'disas main' | gdb ./a.out
gcc -fverbose-asm -g -Wa,-adhln=sum.lst sum.c gcc -S -o- sum.cThanks for the tip.
(The gdb disassembly shows memory offsets, which might be helpful for some purposes.)
As a human, I can see that the loop has no effects except to increase s by 1 each of NUMBER times, and that the result of doing so would increase s by 1 * NUMBER, and that s started as 0, so s ends up as NUMBER. (Also the loop variable i is incremented NUMBER times so it ends up as NUMBER too, but its value is never referred to again.)
I'm wondering how the optimizer observed and represented these facts, and how much more general its attempts at optimizing this loop were.
LLVM also has a pass dedicated to detecting loop idioms: http://llvm.org/docs/doxygen/html/LoopIdiomRecognize_8cpp_so...
(I'm not at all familiar with how GCC is architected, sorry)
When we have a computer that can read the original post and give estimates and comment here on HN I will be impressed. Until then it's just a faster z80 to me, amazing, don't get me wrong, the things we can do today with the power at our disposal starts to feel like magic. [1]
All that said it makes me sad when I find code that someone didn't bother to think through or even use the profiling tools available to maximize the amount of resources it's consuming. It's true that "premature optimization is the root of all evil"[2] however at some point it can be worth you time to review your assumptions and crappy code and give it a tune up.[3]
[1] https://en.wikipedia.org/wiki/Clarke%27s_three_laws [2] https://en.wikiquote.org/wiki/Donald_Knuth [3] http://ubiquity.acm.org/article.cfm?id=1513451
No, we can't even comprehend.
It's possible, but you really notice whenever things fail to be cached. (I'm looking at you google maps!)
Understanding that "computers are fast" (even in python!) is a very important step towards understanding where we make them slow and whether that is because of waste or because the task is naturally expensive.
Based on your skepticism i assume that you just haven't had much exposure to people who are really bad at these thing, despite having all the formal education (and the paycheck to match). "I'm working in ${absurdly high level language}, of course i'm not supposed to care for performance" is what they tell you before venturing off to make a perfectly avoidable performance blunder that would be crippling even in fully vectorized assembly, followed by a few days spent transforming all their code a different, but perfectly equivalent syntactic representation that looks a bit faster.
& probably there are more python coders out there that could benefit from developing this kind of thinking than C programmers, so it makes sense from that perspective, too.
(Side note: It's a trickier exercise in python than in C, which is itself a trickier exercise than plain assembly.)
Where did you get this information from? Learning C is easy. K&R is under 300 pages long, you could get through it in a weekend:
https://en.m.wikipedia.org/wiki/The_C_Programming_Language
After that you could pick up another more modern book, or could just learn as you go.
What I'd also say is that you don't need to be a master of C to write faster code than Python. I understand the appeal of keeping a codebase in a single language, but it's worth remembering you pay a cost for doing so.
I've found the most effective way for me to learn a new language is to watch or read some introductions to a language (to get a feel for idiomatic usage), then explore further through playing with small projects written in that language. That's what works for me. K&R is sold as a concise introduction to C, so it has value for learners like me even with skipping the exercises. Of course for people who prefer to learn methodically doing the exercises will be more beneficial.
Memory management isn't as hard to grasp as you might think. It's really just an extra step when it comes to handling variables, if you make plans to handle errors then it is straightforward to manage, especially for small libraries, which is what you're likely to write to link up to Python.
method Hz comment
--------------------------------------------------------
empty file 500 an empty file gets passed to /bin/sh
dynamic libc 1000 "int main { return 0; }" -> gcc
static libc 1500 the same, but with "gcc -static"
assembly 2000 see below
bash builtin 150000 avoids hitting the kernel or filesystem
The empty file is the "traditional" implementation of true on Unix.The assembly solution was my attempt at doing the littlest amount possible, because libc initialization still takes time:
.globl _start
_start:
movl $1, %eax # %eax = SYS_exit
xorl %ebx, %ebx # %ebx = 0 (exit status)
int $0x80 a >>$LOG
b >>$LOG
⋮
c >>$LOG
to {
a
b
⋮
c
} >>$LOGI think the really big thing is to actually create some infrastructure around your product to run performance tests whenever you're developing a feature. That's the only way you're ever going to good data.
As an example, the SQL tests will act very differently depending on if the table was in the buffer pool, or it had to be fetched from disk (I wrote my own tool to run tests on MySQL if anyone is interested, https://github.com/arianitu/sql-stress)
I'm surprised by the poor memory performance in his tests; my machine get's around an order of magnitude better performance in terms of throughput; which leads me to believe he's compiling using a very outdated gcc, and/or has really slow memory (laptops- you never know), and/or (reasonable, since he only mentioned -O2, but depends on the bitness of the compiler) he's compiling in "compatibility with 80386" mode.
I think it's odd that people still haven't quite figured that one out yet. People use "-O2" all over the place, when that's rarely faster than "-O3", and they leave out one of the simplest optimization options the compiler has - "-march=native".
It defaults to 12, IIRC.
But if some match, and if it is ignoring case, it's much slower. It's actually faster to read the whole file into memory, lowercase it and check with python for index of match.
Basically, the idea is that you build a deterministic finite state automaton and try feeding the string through it. Each character would cause exactly one automaton transition. Therefore, you can do the whole thing in O(n) after you pay the cost of preprocessing to build the automaton, with a quite tiny constant for small patterns.
It makes sense if you think about a search string that has the same length as the searched string. If they don't match you can find that out with a single character comparison.
I have 4434 files with about 100 words of text in them.
here is python func that searches utf8:
def find_sp(paths):
for path in paths:
with open(path,'rb') as f:
if b"snow" in f.read().lower():
yield True
else:
yield False
%timeit [o for o in find_sp(glob.glob('./*'))]
10 loops, best of 3: 35.2 ms per loop
Here is the bash one: $>echo $LANG
C
$>time grep -Riq "snow" .
real 0m0.015s
user 0m0.005s
sys 0m0.010s
However, if you have a lot of files and you are doing the searching for them in a not very smart way, it takes a lot longer. time for f in ./*txt; do grep -iq "snow" $f;done
real 0m2.307s
user 0m0.337s
sys 0m2.113sTo make the comparison more fair you'd store the python program and time starting and running both from a shell.
Even in a native program heap allocations can slow something down to 1/7th.
After that memory ordering for cache locality can still gain 10x - 25x speedups.
After that proper SIMD use (if dealing with bulk numeric computations) can buy another 7x (that's the most I've gotten out of AVX and ISPC.
Then proper parallelism and concurrency are still on the table (but you better believe that the concurrency can be very difficult to make scale).
The divide between how fast software can potentially run and how fast most software actually runs is mind blowing.
As an example, for the first test I got:
pypy test.py 1000000000 1.01s user 0.03s system 99% cpu 1.042 total
python test.py 55000000 1.02s user 0.01s system 99% cpu 1.038 total
So about 18 times faster. On most tests PyPy was 3-10 times faster than cPython. So what does this tell us? Nothing really, the benchmarks are not really indicative of anything you would do with Python. Oh, and PyPy is very fast at some stuff.
It's exponential. It's worse than a shell loop spawning a new echo process every iteration.
Reported: 2005-03-30, unpatched to this day, because parsing opened files on the fly recursively with O(2^n) complexity is enough.
First of all, the code as written will just optimize to nothing, so we need to add an asm("" : "=g" (s) : "0" (s)) in the loop to stop strength reduction and autovectorization, and we need to return the final value to stop dead code elimination.
Once that is done, the result is more than 2 billion iterations per second on a ~3 GHz Intel desktop CPU, while the author gives an absurd value of 500m iterations which could not have been possibly obtained with any recent Intel Xeon/Core i5/i7 CPU.
BTW, the assembly code produced is this:
1:
add $0x1,%edx
add $0x1,%esi
cmp %eax,%edx
jne 1b
Which is unlikely to take more than 1/2 cycles to execute on any reasonable CPU as my test data in fact shows.
But yeah, I was surprised by the number of operations per second too. I was thinking it had to be over a billion.
For less than 20 cents (in quantity, perhaps) you can buy a chip that out-performs the personal computers available in the early 80s. Of course you have to add peripherals to bring it to true parity, but you can probably have a working board for about five bucks that'll run rings around an Apple II or a vintage PC. The keyboard and monitor are the most expensive components.
Likewise, memory. Recently I was thinking about doing some optimization and reorganization of some data for a hardware management project, when I realized that the data, for the entire life of the project, would fit into the CACHE of the processor it runs on. Projecting out five or six years, it would always fit. I stopped optimizing.
Most of the time, the most valuable resource is the time of the person involved. Shaving milliseconds of response time rarely matters, shaving an hour of dev time does. (There are big exceptions to this when you are resource-constrained, as in video games, or hardware environments that need to use minimal memory or cycles for cost reasons).
Premature optimization still remains a great evil.
Hell, you can do even better than that. Assuming that CHIP (https://www.kickstarter.com/projects/1598272670/chip-the-wor...) delivers on it's Kickstarter, for $9 you get a 1 GHz CPU and 512 MB RAM. That's roughly on par with an average home PC from about 2002-2003.
If you bump your budget up to $40, you get a Raspberry Pi 2 with a quad core 1 GHz chip and 1 GB of RAM. Now we're talking parity with an typical home PCs from 10 years ago, or less.
After optimising the scheduling a bit (which thanks to Halide is only 6 lines of code), I got that up to 640MP/s.
When I scheduled it for my Iris 6100 (integrated) GPU through Metal (replace the 6 lines of CPU schedule with 6 lines of GPU schedule), I got that up to ~800MP/s.
Compare this to naïvely written C and the difference is massive.
I think it's amazing that my laptop can process nearly a gigapixel worth of data in under a second. Meanwhile it takes ~7s to load and render The Verge.
In any case, you can do a lot better than merely 33 million: e.g. http://primesieve.org/ uses some seriously optimised code and parallelism to count the primes below some number between 10 billion and 100 billion in a streaming fashion (meaning very small memory use). For non-streaming/caching the results, I'm not sure how primesieve does, but my own primal[0] (which is heavily inspired by primesieve) can find the primes below 5 billion and store everything in memory in 1 second using ~170 MiB of RAM on my laptop (and it doesn't support any parallelism, at the moment), and the primes below 500 million in ~0.75 seconds on a Nexus 5, and ~1 second on a Nexus S (although both devices give very inconsistent timings).
I've had to modify the python code in a few places ... don't know why it isn't working out of the box - feel like I must be doing something wrong.
or is it macbook with the fastest consumer grade ssd on the market (until yesterday I think)? :)