Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Here's mantra to repeat until you prove otherwise, "My application is not CPU bound it's IO bound". Even most applications that max the CPU are still IO bound because of their IO access patterns. Seriously, think about how your app is IO bound not CPU bound.

It's the cache thrashing / memory IO / disk IO constraints not the number of threads. People just increase the number of threads to increase the chance that there is data in the cache that the CPU can use.

Trim your data structures so they fit in a CPU cache line, make sure your data structures are properly aligned, make sure all the RAM slots on your server are in use. Put more RAM in your servers, throw out your spindle disk and replace them with SSDs, check your switches, can they actually handle a full 1Gbs burst? Or do they start dropping packets? Are you using all the network ports on your server? Are your switch interconnects 10Gbps? Is close-to-open cache coherence on NFS killing your cache hit rates? Are your indexes fragmented? Is your memory fragmented? Are your splitting your IO so that sequential IO goes to spindles and random IO goes to SSDs? Are your disk partitions aligned to your RAID block size. (Hint: If you're using RAID and your partition starts at sector 1 you're losing 15% of your IO performance) Do you have a cache in front of your app servers that will cache the response so the server thinks it's communicating with the client at a full 1Gbps? I'm not talking about something like varnish, just a simple store and forward cache.

The GIL is just a red herring so that people can point to something they have no control over and can give up hunting for performance.

It's not the GIL it's your IO subsystems. And turn your random IO into sequential IO, here's an example, lets say you have to store a whole bunch of 40K jpegs, instead of writing them as individual files put 1000 of them into an uncompressed ZIP file. Now your IO is sequential instead of random and you've taken a whole bunch of load off the kernel. If you're running on a 7200 RPM disk your throughput just went from 5-10MB/sec to ~100-200MB/sec



I'd up vote this 100x if I could. From my experience dealing with large scale build systems (building 10k - 100m SLOC every 12 hours or so) nothing matters more than IO throughput. Better network architecture, fatter pipes, faster drives, more RAM, bigger L3/L2/L1 caches will all almost always have more impact than more or faster cores.

Most of the time, as you point out, multithreading only ameliorates the IO cost because it allows something in RAM or cache to be processed. If those additional threads need to hit network or disk, however, you're boned and get significantly less benefit. In many cases, you can get a bigger win by not multithreading and simply being smarter about your IO.

Also worth reiterating, something raised both by you and the article: MEASURE! You know nothing about how to optimize your app or system until after you've very thoroughly measured, questioned the results, and re-assessd your assumptions. So often design assumptions made when writing your app or building your system turn out not to be true after measurement.


Sounds like something that would benefit highly from putting your builds on a RAM disk.

If you assume 100M SLOC @ 100 bytes per line you're looking at only ~10GB of RAM. That's only an extra $160 per build machine. (2x4GB DDR3 sticks @ $80 each)

I shaved ~10 minutes off a ~15 minute build and unit test time by putting the SQL DB on RAM and the source tree in RAM. SQL was by far the biggest win. The source tree only saved us about 30 seconds.

If you're on Windows just tick the Advanced Write Optimization box on your drive and all your writes will go to RAM. I think there is something similar on Linux. It sounds like you're the type of person who probably already knows this :)

L2/L3 cache is absolutely huge but the biggest wins by far IMHO are battery backed write cache. The really shitty thing is most BBWC controllers use 50% of the cache as read cache by default which is a total waste IMO. I find about 10% read cache is optimal on the controller. If you can't afford SSDs and still need to guarantee consistency.


That's a false dichotomy, especially for most Rails applications, and one that can easily be shown false using tools like NewRelic.

Of course YMMV depending on the nature of your Ruby application, but in general (and particularly for Rails applications) they are quite CPU hungry. Assembling HTML, XML, and JSON can be quite CPU intensive, especially for large documents. This can be mitigated to a certain extent with techniques like fragment caching (or page caching, which prevents requests from ever hitting the Rails stack)

Sure, a large part of the Rails request cycle is going to be spent in I/O wait on databases or other external services. But after that wait is over, the app gets to work synthesizing that data into a response to send back to the client, and that process is typically CPU intensive.


I use Ruby in a research lab for scientific computing, and in our case, most of our applications are definitely CPU bound. We end up re-writing many of them in C to speed them up and use threading but if we could keep them in Ruby while still using concurrency the faster development times would improve the lives of everyone in the lab. Ruby is not Rails. I for one would benefit immensely from being able to run threaded Ruby code.


You should not write CPU-bound scientific computing code in the language at the bottom of the Shootout: http://shootout.alioth.debian.org/u64q/which-programming-lan...

The easiest move might be to NumPy, assuming it can express your computations in its methods because if you're reduced to doing math in Python you're hardly any better off.

If you're willing to pay the price for Ruby there are certainly other viable options that aren't C. Learning the minimum Haskell to do decently fast math wouldn't be that hard and would not involve to much of the "weird stuff", for instance.


Much of our code is not CPU bound, and the PI is something of a Ruby evangelist. If everyone jumps away from Ruby it will never improve. That being said, I don't think Ruby will ever be the fastest language, but speeding things up is always a good goal.


I'd highly suggest you consider JRuby + Mirah, or IronRuby + F#. F# especially with units is a really nice way to write your math intensive code. See F# vs. C on the Burrows Wheeler Transform.

Mirah has familar JRuby syntax but would be perfect for your scientific code as IIRC function calls are early-bound not late-bound. http://www.mirah.org/


Another huge one on Windows and especially .NET is if you see a MemoryStream replace it with a FileStream that uses DELETE_ON_CLOSE semantics. It bypasses the .NET GC and lets the kernel decide whether it actually wants to write the file out. So if you've got a 1GB file that you need temporarily it will write part of it to disk depending on how much RAM is available.


What's a use case for that final example? I can't help but thinking it would only impede overall performance rather than enhance it.

It may be faster when writing, but most systems are write little, read a lot.


But read load generally follows square law, your most popular files are going to be used an order of magnitude more than your least popular files. If you have 1TB of files you can probably get away with 1GB of RAM for caching with a 90% hit rate. This means that your read speed is something on the order of 11GB/sec. (eg. more than your 1Gbps network) Because ZIP files are essentially append only you can read from a ZIP file while it's being written to as long as you cache the file table. This means that you can have hundreds of readers from the ZIP file and only one writer. UNIX/Windows file locking semantics support this beautifully, readers don't lock the file and writers obtain a write lock.

The basic design of the system is that the readers register an event handler with the writer and when the writer finishes writing a file it updates the readers with the location of the file in the ZIP file. Once you have that information it's a simple read call to the ZIP file.


What you described is still not going to be faster than just using the file system. It's just an unnecessary layer on top of it.


You should probably test that assumption.


I want to, but it's hard to make an artificial test for this. You need to have a dataset that's larger than available ram and a read queue that follows real life access patterns but also uses different enough data to force disk reads to happen.

If the theory checked out I would go ahead and implement it - but the theory says it would be slower.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: