C Weird stdio behavior with buffering disabled

zirias@

Developer
Consider this test program:
C:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

char dummy[16*1024];

int main(void)
{
    FILE *nulldev = fopen("/dev/null", "w");
    printf("written: %zu\n", fwrite(dummy, 1, sizeof dummy, nulldev));
    fclose(nulldev);

    nulldev = fopen("/dev/null", "w");
    setvbuf(nulldev, 0, _IOFBF, sizeof dummy);
    printf("written: %zu\n", fwrite(dummy, 1, sizeof dummy, nulldev));
    fclose(nulldev);

    nulldev = fopen("/dev/null", "w");
    setvbuf(nulldev, 0, _IONBF, 0);
    printf("written: %zu\n", fwrite(dummy, 1, sizeof dummy, nulldev));
    fclose(nulldev);

    int nullfd = open("/dev/null", O_WRONLY);
    printf("written: %zu\n", (size_t)write(nullfd, dummy, sizeof dummy));
    close(nullfd);
}

It writes 4 times 16kiB worth of zeros to /dev/null and always outputs 4 times written: 16384 (which is required for fwrite(3) by the C standard if no write error occurs, and is at least expected for raw write(2) as well).

But now, look at truss(1) to see the actual syscalls performed:
Code:
mmap(0x0,135168,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0x0) = 34924384256 (0x821a7d000)
mprotect(0x2b5e1ab20000,4096,PROT_READ)          = 0 (0x0)
issetugid()                                      = 0 (0x0)
sigfastblock(0x1,0x2b5e1ab22840)                 = 0 (0x0)
open("/etc/libmap.conf",O_RDONLY|O_CLOEXEC,04152000030) = 3 (0x3)
fstat(3,{ mode=-rw-r--r-- ,inode=332418,size=47,blksize=4096 }) = 0 (0x0)
read(3,"# $FreeBSD$\nincludedir /usr/loc"...,47) = 47 (0x2f)
close(3)                                         = 0 (0x0)
open("/usr/local/etc/libmap.d",O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC,0165) = 3 (0x3)
fcntl(3,F_ISUNIONSTACK,0x0)                      = 0 (0x0)
getdirentries(3,"_\M-e\^Z\0\0\0\0\0\^A\0\0\0\0\0"...,4096,{ 0x0 }) = 104 (0x68)
open("/usr/local/etc/libmap.d/mesa.conf",O_RDONLY|O_CLOEXEC,0165) = 4 (0x4)
fstat(4,{ mode=-rw-r--r-- ,inode=1347155,size=38,blksize=4096 }) = 0 (0x0)
read(4,"libGLX_indirect.so.0 libGLX_mesa"...,38) = 38 (0x26)
close(4)                                         = 0 (0x0)
getdirentries(3,0x821a82008,4096,{ 0x1a15d221 }) = 0 (0x0)
close(3)                                         = 0 (0x0)
open("/var/run/ld-elf.so.hints",O_RDONLY|O_CLOEXEC,010002706) = 3 (0x3)
read(3,"Ehnt\^A\0\0\0\M^@\0\0\0\M^R\^A\0"...,128) = 128 (0x80)
fstat(3,{ mode=-r--r--r-- ,inode=393218,size=530,blksize=4096 }) = 0 (0x0)
pread(3,"/lib:/usr/lib:/usr/lib/compat:/u"...,402,0x80) = 402 (0x192)
close(3)                                         = 0 (0x0)
open("/lib/libc.so.7",O_RDONLY|O_CLOEXEC|O_VERIFY,00) = 3 (0x3)
fstat(3,{ mode=-r--r--r-- ,inode=225660,size=1943128,blksize=131072 }) = 0 (0x0)
mmap(0x0,4096,PROT_READ,MAP_PRIVATE|MAP_PREFAULT_READ,3,0x0) = 34937102336 (0x82269e000)
mmap(0x0,4190208,PROT_NONE,MAP_GUARD,-1,0x0)     = 34952810496 (0x823599000)
mmap(0x823599000,532480,PROT_READ,MAP_PRIVATE|MAP_FIXED|MAP_NOCORE|MAP_PREFAULT_READ,3,0x0) = 34952810496 (0x823599000)
mmap(0x82361b000,1351680,PROT_READ|PROT_EXEC,MAP_PRIVATE|MAP_FIXED|MAP_NOCORE|MAP_PREFAULT_READ,3,0x81000) = 34953342976 (0x82361b000)
mmap(0x823765000,40960,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_PREFAULT_READ,3,0x1ca000) = 34954694656 (0x823765000)
mmap(0x82376f000,28672,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_PREFAULT_READ,3,0x1d3000) = 34954735616 (0x82376f000)
mmap(0x823776000,2236416,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_ANON,-1,0x0) = 34954764288 (0x823776000)
munmap(0x82269e000,4096)                         = 0 (0x0)
close(3)                                         = 0 (0x0)
mprotect(0x823765000,36864,PROT_READ)            = 0 (0x0)
sysarch(AMD64_SET_FSBASE,0x820ee1020)            = 0 (0x0)
mprotect(0x823765000,36864,PROT_READ|PROT_WRITE) = 0 (0x0)
mprotect(0x823765000,36864,PROT_READ)            = 0 (0x0)
readlink("/etc/malloc.conf",0x820ee0710,1024)    ERR#2 'No such file or directory'
issetugid()                                      = 0 (0x0)
mmap(0x0,2097152,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON|MAP_ALIGNED(21),-1,0x0) = 34930163712 (0x822000000)
cap_getmode({ 0 })                               = 0 (0x0)
open("/dev/hpet0",O_RDONLY|O_CLOEXEC,00)         = 3 (0x3)
mmap(0x0,4096,PROT_READ,MAP_SHARED,3,0x0)        = 34967486464 (0x824398000)
close(3)                                         = 0 (0x0)
mmap(0x0,2097152,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON|MAP_ALIGNED(12),-1,0x0) = 34937163776 (0x8226ad000)
mmap(0x0,4194304,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON|MAP_ALIGNED(21),-1,0x0) = 34972106752 (0x824800000)
mprotect(0x202000,4096,PROT_READ)                = 0 (0x0)
open("/dev/null",O_WRONLY|O_CREAT|O_TRUNC,0666)  = 3 (0x3)
fstat(3,{ mode=crw-rw-rw- ,inode=35,size=0,blksize=4096 }) = 0 (0x0)
ioctl(3,TIOCGETA,0x820ee18e0)                    ERR#25 'Inappropriate ioctl for device'
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,4096) = 4096 (0x1000)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,4096) = 4096 (0x1000)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,4096) = 4096 (0x1000)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,4096) = 4096 (0x1000)
fstat(1,{ mode=crw-rw-rw- ,inode=35,size=0,blksize=4096 }) = 0 (0x0)
ioctl(1,TIOCGETA,0x820ee1500)                    ERR#25 'Inappropriate ioctl for device'
close(3)                                         = 0 (0x0)
open("/dev/null",O_WRONLY|O_CREAT|O_TRUNC,0666)  = 3 (0x3)
fstat(3,{ mode=crw-rw-rw- ,inode=35,size=0,blksize=4096 }) = 0 (0x0)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,16384) = 16384 (0x4000)
close(3)                                         = 0 (0x0)
open("/dev/null",O_WRONLY|O_CREAT|O_TRUNC,0666)  = 3 (0x3)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,1024) = 1024 (0x400)
close(3)                                         = 0 (0x0)
openat(AT_FDCWD,"/dev/null",O_WRONLY,00)         = 3 (0x3)
write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"...,16384) = 16384 (0x4000)
close(3)                                         = 0 (0x0)
write(1,"written: 16384\nwritten: 16384\n"...,60) = 60 (0x3c)
exit(0x0)                                       
process exit, rval = 0

Ok, the standard buffer size seems to be just 4kiB, pretty small for today, but you can change it ... and after changing it to 16kiB, the whole chunk is written at once.

But then, with buffering disabled, it only ever writes 1kiB? I would have expected it to try writing the whole buffer passed instead (and resume on short writes).

For comparison, this is what glibc on Linux does (strace output):
Code:
execve("./writetest", ["./writetest"], 0x7ffccad405e0 /* 19 vars */) = 0
brk(NULL)                               = 0x55ad5781f000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f129368f000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=26922, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 26922, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f1293688000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220s\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=1922136, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 1970000, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f12934a7000
mmap(0x7f12934cd000, 1396736, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x26000) = 0x7f12934cd000
mmap(0x7f1293622000, 339968, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x17b000) = 0x7f1293622000
mmap(0x7f1293675000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ce000) = 0x7f1293675000
mmap(0x7f129367b000, 53072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f129367b000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f12934a4000
arch_prctl(ARCH_SET_FS, 0x7f12934a4740) = 0
set_tid_address(0x7f12934a4a10)         = 2658583
set_robust_list(0x7f12934a4a20, 24)     = 0
rseq(0x7f12934a5060, 0x20, 0, 0x53053053) = 0
mprotect(0x7f1293675000, 16384, PROT_READ) = 0
mprotect(0x55ad56028000, 4096, PROT_READ) = 0
mprotect(0x7f12936c1000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7f1293688000, 26922)           = 0
getrandom("\x28\x30\x1a\x30\x2e\x00\xbd\x7e", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x55ad5781f000
brk(0x55ad57840000)                     = 0x55ad57840000
openat(AT_FDCWD, "/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
newfstatat(3, "", {st_mode=S_IFCHR|0666, st_rdev=makedev(0x1, 0x3), ...}, AT_EMPTY_PATH) = 0
ioctl(3, TCGETS, 0x7ffe90d579d0)        = -1 ENOTTY (Inappropriate ioctl for device)
write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
newfstatat(1, "", {st_mode=S_IFCHR|0666, st_rdev=makedev(0x1, 0x3), ...}, AT_EMPTY_PATH) = 0
ioctl(1, TCGETS, 0x7ffe90d57400)        = -1 ENOTTY (Inappropriate ioctl for device)
close(3)                                = 0
openat(AT_FDCWD, "/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
newfstatat(3, "", {st_mode=S_IFCHR|0666, st_rdev=makedev(0x1, 0x3), ...}, AT_EMPTY_PATH) = 0
ioctl(3, TCGETS, 0x7ffe90d57a70)        = -1 ENOTTY (Inappropriate ioctl for device)
write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
close(3)                                = 0
openat(AT_FDCWD, "/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
close(3)                                = 0
openat(AT_FDCWD, "/dev/null", O_WRONLY) = 3
write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
close(3)                                = 0
write(1, "written: 16384\nwritten: 16384\nwr"..., 60) = 60
exit_group(0)                           = ?
+++ exited with 0 +++

Does anyone know a reason for this unexpected behavior in FreeBSD's libc.so with buffering disabled (_IONBF)? Or is this just some bug?

Background discovering this was a tool where I already have large buffers, so disabling stdio buffering seems like an obvious optimization to avoid needless copying of data, but of course if the price is a vastly increasing number of syscalls, this is pretty bad ...
 
What is the underlying filesystem? What does stat(2) give you for st.st_blksize? There is "seek optimization" going on looking at it.
 
You do not perform error checking. Does setvbuf(3) return 0 (success) in all cases? Maybe _IONBF can’t be honored, especially in conjunction with a character device (/dev/null).​
 
in src/lib/libc/stdio/fvwrite.c in__sfvwrite
it writes max BUFSIZ which is 1024 (defined in stdio.h)

this is on 12.x so seems like its like this from some time
 
What is the underlying filesystem?
devfs(8) 😈. It happens in the same way on stdout (to a terminal) and on an actual file on ZFS.

You do not perform error checking. Does setvbuf(3) return 0 (success) in all cases? Maybe _IONBF can’t be honored, especially in conjunction with a character device (/dev/null).
Which is on purpose for a minimal example to show the undesired behavior. And as you see the behavior changed each time, it obviously succeeds.

in src/lib/libc/stdio/fvwrite.c in__sfvwrite
it writes max BUFSIZ which is 1024 (defined in stdio.h)

this is on 12.x so seems like its like this from some time
Thanks ... but does that make any sense? Will be a real performance killer for applications doing their own buffering.
 
no idea why is that
looking at the code which is kind of ugly ... maybe if you set a full buffer of size 0 or 1 you will get what you want (write thru)
did not test
 
In my actual code, I now just use the POSIX write(2) instead when available, completely sidestepping this issue.

I still wonder whether this actually qualifies as a "bug" 😉

(edit: well, not a definite bug, it's behaving within the C language spec ... but one of the "braindamage" kind ....)
 
I still wonder whether this actually qualifies as a "bug" 😉

I would report this to the FreeBSD foundation. They have a grant upcoming to work on libc. It is about SIMD instructions but you never know, maybe you can motivate the individual doing the libc work to look into this, too.
 
Why are you using stdio if you don’t want its buffering? It needs some internal buffering for printf & friends. Not a bug. If you’re worried about performance, use a larger vbuf or just use write(2).
 
Why are you using stdio if you don’t want its buffering?
stdio is portable everywhere a hosted C environment exists.

It needs some internal buffering for printf & friends. Not a bug.
That's not the stream buffer, therefore won't be used for fwrite(3). The stream buffer's purpose is better performance, by reducing the number of system I/O calls.

If you’re worried about performance, use a larger vbuf or just use write(2).
Or no buffer at all, which is explicitly supported by C and the obvious choice when doing your own buffering. A large buffer would just cause lots of copying in memory. write(2) is POSIX-only. I do use it now, as already mentioned, but only when building on/for a POSIX-compliant system. For FreeBSD, this is a suitable workaround.
 
A big reason to love FreeBSD; it’s trivial to find and look at the code: https://github.com/freebsd/freebsd-...d195de9745678e28/lib/libc/stdio/fvwrite.c#L91

If unbuffered, setvbuf(3) tells us the design is to get (some) data AS SOON AS IT IS WRITTEN to the output. Obviously it can’t be at the same cycle, but my guess is this is where the small 1024 (since BSD 4.4 lite 31 years ago, according to git) value comes from. We could debate if 1k is the right size after 31 years, but I don’t want to be the one chasing down exciting new bugs from it. ;)

Also note, further down, if you fwrite() >= than the buffer size and the buffer is empty, there is no buffering — it directly calls write() on buffer-sized-chunks.

So if you’re doing your own buffering, set the buffer to point to a buffer of the size of transactions you’ll typically be writing out and you’ll always get a single (transaction/buffer-sized) write() without buffering from fwrite().

Edit: If at some point you write a fractional buffer, this will get you out of rhythm, and subsequent fwrite() calls of the set buffer size will instead (fill (copy) + flush, followed by partial fill (copy)) -- you'll still get buffer-size writes going out, but not all of the data will go out with each fwrite(); a fflush() would be your friend if being to get back in 'sync' and avoiding copies.
 
To clarify first, the question I'd like to see answered is why does FreeBSD's stdio behave that way? Because, if there's no solid reason (and no, "it's been like this since 4.4BSD" doesn't count here), I'd consider it a bug of some kind. As I said, it doesn't violate the C standard, but it does violate the sensible assumption that disabling the buffering causes stdio to mess with your reads and writes as little as possible.

So, looking at the code, this question still isn't answered.

Eric A. Borisch you mainly analyzed the code path for "full buffering", and it indeed looks like your suggested way to "trick" stdio into writing efficiently would kind of work. Still I'd never write this kind of code, using intimate/undocumented knowledge about the implementation to get a desired result is the exact opposite of robust and portable code. As already mentioned, my solution was adding support for the POSIX "raw" I/O functions, preferring them when available. Coincidentally, I have a somewhat similar strategy in my own stream buffering code. A write request not fitting into the buffer will first fill it up, flush it, and have the rest written directly. I could probably optimize that to not even touch the buffer when it's empty, like FreeBSD's stdio does.

But then, why does FreeBSD's stdio still insist to never write more than the current buffer size at once, even though the underlying write(2) imposes no such restriction? Maybe it did at some distant point in history?

Thinking about that, at least the pattern gets visible, looking at the "no buffer" case a few lines above: https://github.com/freebsd/freebsd-...d195de9745678e28/lib/libc/stdio/fvwrite.c#L83

There's this "chunked writing" again, the only difference being without a buffer, it can't use the buffer size and uses BUFSIZ instead, which is ridiculously small, leading to the weird situations that the chunks written get even smaller when disabling the buffer.

I can't see a reason why just passing len there instead of capping it to BUFSIZ first should do any harm. Please tell me if you know I'm wrong about that. 😉
 
To clarify first, the question I'd like to see answered is why does FreeBSD's stdio behave that way? Because, if there's no solid reason (and no, "it's been like this since 4.4BSD" doesn't count here), I'd consider it a bug of some kind. As I said, it doesn't violate the C standard, but it does violate the sensible assumption that disabling the buffering causes stdio to mess with your reads and writes as little as possible.

[...]

But then, why does FreeBSD's stdio still insist to never write more than the current buffer size at once, even though the underlying write(2) imposes no such restriction? Maybe it did at some distant point in history?

Thinking about that, at least the pattern gets visible, looking at the "no buffer" case a few lines above: https://github.com/freebsd/freebsd-...d195de9745678e28/lib/libc/stdio/fvwrite.c#L83

There's this "chunked writing" again, the only difference being without a buffer, it can't use the buffer size and uses BUFSIZ instead, which is ridiculously small, leading to the weird situations that the chunks written get even smaller when disabling the buffer.

I can't see a reason why just passing len there instead of capping it to BUFSIZ first should do any harm. Please tell me if you know I'm wrong about that. 😉

Gotcha. Trying something like this would be interesting:

C:
    if (fp->_flags & __SNBF) {
        /*
         * Unbuffered: multiples of BUFSIZ bytes at a time when possible.
         */
        do {
            GETIOV(;)
            w = len > BUFSIZ ? len - (len % BUFSIZ) : len;
            w = _swrite(fp, p, w);
            if (w <= 0)
                goto err;
            p += w;
            len -= w;
        } while ((uio->uio_resid -= w) != 0);
    } else if ((fp->_flags & __SLBF) == 0) {

This would try to have somewhat similar behavior as the previous code, insofar as the completed writes will be multiples of BUFSIZ or full requested size.
 
Eric A. Borisch why do you think so? I could maybe think of advantages from aligned memory, but then, the buffer passed to _swrite() here is user-supplied and most likely not aligned. And there will always be "short" writes as well.

I'd naively assume the most efficient thing to do is write as much as possible in one go, IOW just initialize w to len...
 
Back
Top