When I was in college, the first assignment was to run a provided benchmark program that jumped all over a gradually growing buffer and graphed average latency vs. buffer size and we had to identify the sizes of thle various caches from the generated graphs. I was tripple-booting Linux Windows, and QNX at the time and did the same assignment on each OS (identical hardware). QNX had more sudden transitions between latency levels due to delaying transition later due to smaller cache footprint.
If your userspace NIC driver and TCP/IP stack are implementee as a library and you are running your high performance application on a dual NIC box, your application can be the driver for one of the NICs and the other NIC can handle everything else. It's hard to beat zero copy/zero context switch I/O. Of course, putting the application in the kernel would yet be faster, at least if in both cases the kernek's cache footpint is tiny.
Assuming you didn't have those mistakes, the difference was likely due to memory allocator choices. Scattering the pages of memory is very different from laying them all out one after another. Scattered pages will cause benchmark results to be less predictable, with evictions seeming to happen at random. QNX probably made one great big allocation, in both the virtual and physical address spaces.
I noticed a later filesystem simulation benchmark for the same class ran faster when I moved my mouse. After a few minutes of investigation, I submitted a patch to prefer /dev/urandom to /dev/random on hosts that had /dev/random.