views:

516

answers:

1

I need to speed up transfers across my gigabit ethernet connection. Right now, I'm doing something almost exactly like this, but I'm only seeing about 40% of that when I run this code below.

I also ran this script on all of my (Mac Pro) machines before testing

#!/bin/bash

sudo sysctl -w net.inet.tcp.win_scale_factor=8
sudo sysctl -w kern.ipc.maxsockbuf=16777216
sudo sysctl -w net.inet.tcp.sendspace=8388608
sudo sysctl -w net.inet.tcp.recvspace=8388608

The actual code follows:

import java.io.*;
import java.nio.*;
import java.net.*;

public class BandwidthTester {
private static final int OUT_BUF = (1 << 17),
                    IN_BUF = (1 << 17), SEND_BUF = (1 << 22), RECV_BUF = (1 << 22);
public static void main(String[] args) {
    try {
        // server
        if (args.length == 0) {
            ServerSocket sock = new ServerSocket();
            sock.bind(new InetSocketAddress(41887));

            // wait for connection
            Socket s = sock.accept();

            s.setSendBufferSize(SEND_BUF);

            System.out.println("Buffers: " + s.getSendBufferSize() + " and " + s.getReceiveBufferSize());

            sock.close();

            BufferedOutputStream bOut = new BufferedOutputStream(s.getOutputStream(), OUT_BUF);

            // send lots of data
            sendLotsOfData(bOut);
        } else if (args.length == 2) {
            String host = args[0];
            int port = Integer.parseInt(args[1]);

            System.out.println("Connecting to " + args[0] + ":" + args[1]);

            Socket sock = new Socket();
            sock.setReceiveBufferSize(RECV_BUF);
            sock.connect(new InetSocketAddress(host, port));

            System.out.println("Buffers: " + sock.getSendBufferSize() + " and " + sock.getReceiveBufferSize());

            BufferedInputStream bIn = new BufferedInputStream(sock.getInputStream(), IN_BUF);
            getLotsOfData(bIn);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}


public static void getLotsOfData(InputStream in) {
    System.out.println("Getting data...");
    try {
        long start = System.currentTimeMillis();

        ByteBuffer intConv = ByteBuffer.allocate(4);

        in.read(intConv.array());
        int len = intConv.getInt(0);
        for (int i=0; i < len; i++) {
            in.read(intConv.array());
            int val = intConv.getInt(0);
        }

        long end = System.currentTimeMillis();

        double elapsed = ((double)(end - start)) / (1000.0);

        System.out.println("Read in " + elapsed + " seconds: " + ( (4.0*8.0*len/elapsed) + " bits per second"));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static void sendLotsOfData(OutputStream out) {
    System.out.println("Sending data...");
    try {
        long start = System.currentTimeMillis();

        int len = (1 << 29);

        ByteBuffer intConv = ByteBuffer.allocate(4);
        intConv.putInt(0, len);
        out.write(intConv.array());
        for (int i=0; i < len; i++) {
            intConv.putInt(0, i);
            out.write(intConv.array());
        }

        out.flush();

        long end = System.currentTimeMillis();

        double elapsed = ((double)(end - start)) / (1000.0);

        System.out.println("Sent in " + elapsed + " seconds: " + ( (4.0*8.0*len/elapsed) + " bits per second"));
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}
}

Any suggestions? It's taking about 42 seconds to send all of that data, but even a 10% improvement here would have a dramatic impact on my program.

+1  A: 

One thing you might try is using a larger buffer for the ByteBuffer. Going from 4 bytes to 16, I went from a 12 second transfer time to a 9 second transfer time. (tested using 2^26 rather then 2^29 for length)

That said, it was being run locally; so no actual network issues should have been encountered.

Somewhat dirty modified code for sending:

ByteBuffer intConv = ByteBuffer.allocate(16);
intConv.putInt(0, len);
out.write(intConv.array(),0,4);
for (int i=0; i < len; i+=4) {
    for(int j=0; j<4; j++)
        intConv.putInt(4*j, i);
    out.write(intConv.array());
}

And Receiving:

ByteBuffer intConv = ByteBuffer.allocate(16);
in.read(intConv.array(),0,4);
int len = intConv.getInt(0);
for (int i=0; i < len; i+=4) {
    in.read(intConv.array());
    for(int j=0; j<4; j++)
    {
      int val=intConv.getInt(j*4);
    }
}

Clearly the receiving end would need some modification to handle strange and odd cases like 'what if there were only 3 ints remaining/read from the stream', but I think this would be enough to see if it improves performance.

CoderTao
right on. the larger the buffer, the closer to 1Gb/s I get.I'm still confused how BufferedInputStream or BufferedOutputStream work. Why would going socket --> buffer --> buffer be so much faster than socket --> buffer?
I would suspect it's some sort of slowdown in going to read/write the buffered stream so many times; while it's faster then trying to read/write a normal stream, you end up also incurring the cost of dealing with the Buffer. That's only rampant speculation though.
CoderTao