I'm wondering when the GatheringByteChannel's write methods (taking in an array of ByteBuffers) have advantages over the "regular" WritableByteChannel write methods.
I tried a test where I could use the regular vs. the gathering write method on a FileChannel, with approx 400KB/sec total in ByteBuffers of between 23-27 bytes in length in both cases. Gathering writes used an array of 64. The regular method used up approx 12% of my CPU, and the gathering method used up approx 16% of my CPU (worse than the regular method!)
This tells me it's NOT useful to use gathering writes on a FileChannel around this range of operating parameters. Why would this be the case, and when would you ever use GatheringByteChannel? (on network I/O?)
Relevant differences here:
public void log(Queue<Packet> packets) throws IOException
{
if (this.gather)
{
int Nbuf = 64;
ByteBuffer[] bbufs = new ByteBuffer[Nbuf];
int i = 0;
Packet p;
while ((p = packets.poll()) != null)
{
bbufs[i++] = p.getBuffer();
if (i == Nbuf)
{
this.fc.write(bbufs);
i = 0;
}
}
if (i > 0)
{
this.fc.write(bbufs, 0, i);
}
}
else
{
Packet p;
while ((p = packets.poll()) != null)
{
this.fc.write(p.getBuffer());
}
}
}
update:
I did some testing and the gathering approach for various lengths of ByteBuffers seems to have no benefit for file I/O. Far more relevant was the "fragmentation" of the I/O stream via byte buffer length. I changed my program so it copied a relatively large (27MB) file via reading the input into byte buffers of a particular length. The program starts to slow down significantly if the buffers are less than 256 bytes in length.
I decided to try a third option, namely to write my own simple "gathering" routine that takes buffers and consolidates them into a larger buffer before writing to a filechannel. This blows away the GatheringByteChannel write(ByteBuffer[] buffers)
method for speed. (Note: The reading size is the same for all three modes of writing, so the fact that I'm creating a bunch of small ByteBuffers and using them to read I/O isn't causing a significant slowdown.) I'm kind of disappointed that Java doesn't just do this for you. Oh well.
enum GatherType { NONE, AUTOMATIC, MANUAL }
static class BufferWriter
{
final private FileChannel fc;
private GatherType gather = GatherType.NONE;
BufferWriter(FileChannel f) { this.fc = f; }
public void setGather(GatherType gather) { this.gather=gather; }
public void write(Queue<ByteBuffer> buffers) throws IOException
{
switch (this.gather)
{
case AUTOMATIC:
{
int Nbuf = 64;
ByteBuffer[] bbufs = new ByteBuffer[Nbuf];
int i = 0;
ByteBuffer b;
while ((b = buffers.poll()) != null)
{
bbufs[i++] = b;
if (i == Nbuf)
{
this.fc.write(bbufs);
i = 0;
}
}
if (i > 0)
{
this.fc.write(bbufs, 0, i);
}
}
break;
case MANUAL:
{
ByteBuffer consolidatedBuffer = ByteBuffer.allocate(4096);
ByteBuffer b;
while ((b = buffers.poll()) != null)
{
if (b.remaining() > consolidatedBuffer.remaining())
{
consolidatedBuffer.flip();
this.fc.write(consolidatedBuffer);
consolidatedBuffer.clear();
}
if (b.remaining() > consolidatedBuffer.remaining())
{
this.fc.write(b);
}
else
{
consolidatedBuffer.put(b);
}
}
consolidatedBuffer.flip();
if (consolidatedBuffer.hasRemaining())
{
this.fc.write(consolidatedBuffer);
}
}
break;
case NONE:
{
ByteBuffer b;
while ((b = buffers.poll()) != null)
{
this.fc.write(b);
}
}
break;
}
}
}