Latency Numbers Every Programmer Should Know
people.eecs.berkeley.edu
people.eecs.berkeley.edu
In fact, it's not just about designs that work, or designs that are fast, but getting into the practice of estimating complexity in terms of hardware numbers also makes for safer code, especially where validating user data is concerned.
Just recently even, it kept me back from what might have been a potential denial of service in https://github.com/ronomon/mime, and lead to discovering a vulnerability in several popular email parsers (https://snyk.io/blog/how-to-crash-an-email-server-with-a-sin...).
I think Martin Thompson summarized it well as "Mechanical Sympathy": https://mechanical-sympathy.blogspot.com/2011/07/why-mechani...
One rather off-topic observation: April 23 to June 25 is somewhat shorter than the 90-day window you mentioned. ("A few days before the 90-day public disclosure deadline...") What was the reason for that? It doesn't appear to be because those who were going to fix it had already done so - they published their fixes after the public disclosure.
(I'm just curious, not criticizing or anything.)
Regarding the 90-day window, you are spot on. I never realized that until now. I made a mistake with the month, it should have been July 25 not June 25, so it came out after 60 days, not 90 days as I intended.
That's evidence #1236577 that I have no clue at all!
Edit: I've never seen any of her videos before. Now I did. She had an amazing sense of humor.
I remember when I first saw this, there were still visible red and green squares on the top rows. Today those numbers are so small, those squares are missing completely!
(That said whoever owns this page should update the scale of the blocks so it's more useful going forward)
Fwiw the "fork me on github" banner points to:
Some assorted facts:
Rotational latency -- The most common hard drive models are 7200 RPM. That is 120 rotations per second, or 1 rotation per 8.33 milliseconds. So, all things being equal, the average rotational latency will be half of that (best case: 0, worse case: 8.33 ms).
Seek time -- The most common hard drive models have 1 head on an actuator (though multi-actuator models are being developed). The actuator has to physical move the head across the surface of the platters so that it's over the bits you want. The worst case seek time is around 8ms on a typical hard drive, for an average of 4ms. The less random your workload is, that is, the smaller subset of the disk you are touching, the smaller your seek time will be.
IO size -- the amount of data you can read in a single hard drive rotation actually depends on where on the platter it is. If it's on the outer diameter of the drive, it might be e.g. 2MB, but on the inside diameter, only 1MB. For the reason you can actually get significantly higher throughput when reading from the outside diameter (typically the lower LBAs). Once you start doing even larger IOs, you're talking about waiting for multiple rotations for the IO to complete, which equals more time.
Read/Write distribution -- Hard drives have buffers on them to absorb writes, and it is asynchronously flushed in a very efficient manner (either when the hard drive is not doing anything, or is on the way to do another IO and happens to pass over the right part of the disk).
Queue depth -- Hard drives allow you to enqueue multiple concurrent IOs (most commonly from different threads/processes). SATA allows for a maximum of 32 concurrent IOs, whereas NVMe goes all the way up to 65K. Usually with an HDD though, you'll be on SATA. The number of concurrent IOs is referred to as the queue depth. You can improve your average latency by increasing queue depth, at the cost of peak latency. The reason for this is that hard drives will re-order queued IOs to reduce rotational latency and seek time. For example, imagine you issue 4 concurrent reads to LBAs 0, 10, 2, 5. The drive will likely reorder this to 0, 2, 5, 10 so that it doesn't have to spend as much time seeking. This will reduce the average latency, but the at particularly high queue depths, a particularly unlucky IO might get deprioritized for 100+ ms. If you care a lot about peak latency, bound your queue depth to 4 or so.
Here's a better list:
https://gist.github.com/eshelman/343a1c46cb3fba142c1afdcdeec...
Highly recommended!
Edit: I just noticed that clicking on it shows box plots. Good enough!
We've got all of our data going back years in AWS Athena, we're just waiting to have time to do something fun with it.
Then try again, but sort the array before you begin (which will make the branch trivial to predict). Time the difference. I think you'll be surprised.
* 1MB: 6.52ms vs 2.16ms (3x speedup)
* 1GB: 5.73s vs 2.25s (2.5x speedup)
Tested with this C code, called as "bpredict-time [size] [sort]":
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/time.h>
#define SIZE (1024*1024)
int cmp (const void *val1,
const void *val2)
{
return *(uint8_t*)val1-*(uint8_t*)val2;
}
int
main (int argc,
char **argv)
{
uint8_t *array;
size_t size = SIZE;
struct timeval start, end;
if (argc > 1) {
size = atoi(argv[1]);
}
array = malloc(sizeof(*array) * size);
if (array == NULL) { return -1;}
for (size_t i=0; i<size; i++) {
array[i] = random();
}
if (argc > 2) {
qsort(array, size, sizeof(*array), cmp);
}
(void)gettimeofday(&start, NULL);
int cnt = 0;
for (size_t i=0; i<size; i++) {
if (array[i] < 128) { cnt++;}
}
(void)gettimeofday(&end, NULL);
printf("N ints < 128: %d\n", cnt);
printf("Time: %f\n", (float)(end.tv_sec-start.tv_sec)\
+(float)(end.tv_usec-start.tv_usec)/1000000);
free(array);
return 0;
}The complaint doesn't show up on the default warning levels. I think only with -W ? (fun fact: -Wall actually has fewer warnings enabled than -W, at least with Clang)
Edit: actually, the warning is enabled by default, but only triggers for functions that have the attribute 'warn_unused_result' set, which is almost none of them, which is why this warning is not well-known. Indeed, in my example my (void) isn't necessary, and I couldn't get the warning to trigger even with -Wpedantic.
There are 5001834 entries out of 10000000 less than 128
Exeuction took 65335182 nanoseconds
There are 5001834 entries out of 10000000 less than 128
Exeuction took 26693952 nanoseconds
Using the following code I hacked together: #include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#define DATASIZE 10000000
#define NSEC_PER_SEC 1000000000
int cmp(const void* a, const void* b)
{
return *(unsigned char*)a - *(unsigned char*)b;
}
int countsmall(unsigned char* data)
{
struct timespec start;
struct timespec end;
u_int64_t delta;
int lcv;
int numsmall;
numsmall = 0;
clock_gettime(CLOCK_MONOTONIC, &start);
for ( lcv = 0; lcv < DATASIZE; lcv++ )
if ( data[lcv] < 128 )
numsmall++;
clock_gettime(CLOCK_MONOTONIC, &end);
delta = (end.tv_sec * NSEC_PER_SEC + end.tv_nsec) -
(start.tv_sec * NSEC_PER_SEC + start.tv_nsec);
printf("There are %d entries out of %d less than 128\n", numsmall, DATASIZE);
printf("Exeuction took %ld nanoseconds\n", delta);
return 0;
}
int main(int argc, char** argv)
{
unsigned char data[DATASIZE];
int lcv;
srandom(time(NULL));
for ( lcv = 0; lcv < DATASIZE; lcv++ )
data[lcv] = random();
countsmall(data);
qsort(data, DATASIZE, sizeof(unsigned char), cmp);
countsmall(data);
return 0;
}https://stackoverflow.com/questions/11227809/why-is-it-faste...
It is about 14 cycles on modern hardware, which is consistent with the 3ns if you have a very high-clocked machine. I think 3ns is rounding down otherwise.
The penalty is not that light if you have heavily optimized code.
Those 14 cycles will be spent with a completely empty pipeline and the code that follows the miss will be starting from scratch - that is, it will likely take time to spin up to filling the pipeline effectively. That is, you have a deep (~14) and wide (~4) pipeline and you're going to be progressively filling it with new work - maybe some of that work has data dependencies so it will be a while before you're filling most slots.
Under enormously ideal conditions you can issue and retire 4 uops (not quite 1:1 with instructions) per cycle - so 14 cycles is passing up the opportunity to execute 56 uops (again, under ideal conditions). Very few codes will get anywhere near this.
However, that is one of the prime reasons that having a really good branch predictor - and it is very good - is so important. It's not just the time spent waiting, it's the fact that you mostly wipe out your pipeline every time.
All this being said, this is actually a very useful and well done diagram. But, imo, it would be even better if it used equal signs in a consistent way.
Can you explain how HFT caused a shorter fiber route across the Atlantic? Is this route open to the non-HFT public?
I read Flash Boys and am aware of a custom fiber link between Weehauken, NJ and Chicago, IL, but I thought they are were moving to microwave. I thought there were some HFT links (fiber or microwave) within Europe.
https://sniperinmahwah.wordpress.com/2016/01/26/hft-in-the-b...
There are apparently shortwave links across the Atlantic:
https://sniperinmahwah.wordpress.com/2018/05/07/shortwave-tr...
https://sniperinmahwah.wordpress.com/2018/07/16/shortwave-tr...
These should have a lower latency than fibre links although the shortwave link will probably have rather a low data rate.
you need a nuclear reactor for transmission and a deep array of detectors buried underground for reception...
I have to say I am disappointed/surprised by how limited those savings are.
But yes the rest of your point is accurate.
https://en.m.wikipedia.org/wiki/Total_internal_reflection
an angle higer than ~42 degrees seems to indicate roughly a 50% longer path?
That said, I wonder what the average "fractal dimension" of a city to city fiber link is - I suspect wavering path around buildings and along roads dominate the increase in path VS theoretical straight (curved along the earth) path?
https://www.nature.com/articles/nphoton.2013.45?error=cookie...
Lacking:
- L3 cache reference - major fault - tlb miss - syscall overhead - Inter-thread latency 64b - threads on same package - Inter-thread latency 64b- across packages - latency to ethernet tx 64b - latency between 2 machines attached to local switch 64 byte transfer
Comments?
(especially if one uses an external USB SATA controller, in the vain hope of having storage that is all three of durable, fast and easily-replaced)
The circumference of the earth is about 40,000 kilometers. A round trip from CA to Netherlends and back will have electricity going roughly that distance. Speed of electricity times 150 milliseconds is 44,000 kilometers.
That means that the number won't change over time.
Surely, the dominant part of the path city to city is via fiber, so laser light?
That doesn't mean that's were most of the time goes (but ideally it should) - for latency things like light-to-signal, buffers, switching, carrier grade nat, all takes its toll.
Would be interesting to see a detailed breakdown of typical data center(... Cell phone, home broadband user) to same, though. Including every "point" in the path from ram, via cache, pci, network card, routers and switches, media converters and up again... For bonus something like USB keyboard touch to glyph on screen over udp or other low latency protocol...
Delay will in most part be because of distance, forwarding delay in routers are a few 10's of microseconds while switches are at the sub-microsecond level. Stuff like this is quick when they have to handle 100Gbit interfaces.
This also implies that you could materially change that rtt number if you used a faster medium - e.g via air by way of microwave towers.
Which is done for high-speed trading networks: https://arstechnica.com/information-technology/2016/11/priva...
function getDCRTT() {
// Assume this doesn't change much?
return 500000; // ns
}
:thinking face emoji:High by 10x in my experience.
Send 2000 byte over network = 44ns Roundtrip in same datacenter = 500000 ns
Isn't a rountrip simply a send from A to B and then a send from node B to node A?
But even if this is assumed to be the time to transfer a UDP packet from one network card buffer to another network card buffer directly connected by a cable this seem extremely low.
That being said, these numbers are roughly identical to what you can expect on AWS.
For the majority of programmers who want to get their shit done with straightforward code, few dependencies and acceptable performance, this is "interesting to know" but not "should know".
If you write unixy tools that do just one job then you often have to deal with this. For example rsync and tar put files into sequential mode and perform readaheads or writebehind-drop the page cache.
And it's not just that kind of tool. At $JOB I did a fairly simple optimization to significantly reduce loadtimes (from NFS) in a render farm by importing a 3rd-party library which provided the necessary libc bindings for readaheads. It's only a dozen lines of code but reduces user-perceived latency from minutes to seconds. The PO was quite happy about not having to pay for hundreds of NVMe SSDs.
> CPU caching is mostly transparent in the ISA
Nonsense. Even in a relatively high language like Java, you can use primitive types like int[] to ensure that certain elements are close to each other in memory. As such, you can have good memory access patterns even in a high language like Java or C#.
I'm fairly certain this stuff is important when choosing data-structures: Vector vs Linked List for instance. Linked Lists are harder to cache than Vectors, and this chart helps explain why both of these O(1) traversals can have dramatically different performance characteristics.
> disk seeking is scheduled by the kernel or in storage controllers, file buffers are cached in the kernel
But you can read a file from beginning to end. Even in a very, very high language like SQL, you can often ensure a high-speed sequential table scan if you write your joins properly. And knowledge of sequential scans can assist you in knowing which indexes to setup for your tables.
Knowing that you have SSDs vs Disks can be helpful in the architecture of SQL architectures.
CPU and data intensive heavy lifting is rarely done in such programs, it is delegated either to specialised libraries or some middleware in the form of a RDBMS. Most of these programs spend most of their time waiting for some IO event, so the few microseconds gained from the vector with a few hundred elements are negligible.
> But you can read a file from beginning to end.
That's what most programs actually do most of the time because files are essentially a stream abstraction. Programs that jump around a file would map it into memory, then the CPU and the kernel would do their best to cache the hot regions, even if the access to these regions is temporally or spatially distant.
How could you make such a blanket statement? is bashing java the new hipster thing to do in programming world nowadays?
But the second...
> That's what most programs actually do most of the time because files are essentially a stream abstraction. Programs that jump around a file would map it into memory, then the CPU and the kernel would do their best to cache the hot regions, even if the access to these regions is temporally or spatially distant.
Just an FYI: you should always use mmap (Linux / POSIX), or File-based Section Objects (Windows). I don't think streams have any performance benefit aside from backwards compatibility, and maybe clarity in some cases.
MMap and the Windows equivalent allows the kernel to share the virtual memory associated with that file across processes. So if the user opens a 2nd, or 3rd version of your program, more of it will be stored "hot" in the RAM.
Since mmap and section objects only use "virtual memory" (of which we have 48-bits worth on Intel / AMD x64 platforms), we are at virtually no risk of running out of ~256TB of virtual RAM available to our platforms.
That's absurd. They aren't as high-performance as C or C++, but Java and C# both have screamingly fast JIT compilers and plenty of high-performance code is written in them. We're not talking about Prolog here. And memory access patterns absolutely makes a huge difference in performance in these languages.
Sure, you CAN ignore that kind of stuff if you want to, but good programmers don't.
Once you use SQL or any external service to your application what you do else often does not matter very much. As long as you keep the big O in the back of your mind, you would not optimize to cache lines etc.
EDIT: downvote is absurd. it's a no-question horrible visualization. I don't know which is worse, the poor presentation or the lack of credit to the original author (Dean).
The linked GitHub repo's description reads "Jeff Dean's latency numbers plotted over time".
third fault, which to me should ban this page from the internet: he has dared to put a "2018" text on his page, insinuating that it's new data, or some new insight, as opposed to being 15+ years old.
when the original text is more understandable than your visualization, you dun goofed