The accepted answer is right, but it doesn’t go into much detail.
One of the key differences between
/dev/rdisk, when you access them from user space, is that
/dev/disk is buffered. The read/write path for
/dev/disk breaks up the I/O into 4KB chunks, which it reads into the buffer cache, and then copies into the user space buffer (and then issues the next 4KB read…). This is nice in that you can do unaligned reads and writes, and it just works. In contrast,
/dev/rdisk basically just passes the read or write straight to the device, which means the start and end of the I/O need to be aligned on sector boundaries.
If you do a read or write larger than one sector to
/dev/rdisk, that request will be passed straight through. The lower layers may break it up (eg., USB breaks it up into 128KB pieces due to the maximum payload size in the USB protocol), but you generally can get bigger and more efficient I/Os. When streaming, like via
dd, 128KB to 1MB are pretty good sizes to get near-optimal performance on current non-RAID hardware.
The caching being done by
/dev/disk’s read and write paths is very simple and almost brain dead. It caches even if not strictly necessary; like if the device could memory map and directly transfer into your app's buffer. It does small (4KB) I/Os, which leads to a lot of per-I/O overhead. It does not do any read ahead or write behind.