1

I am trying to convert an image to a byte array so that I can transfer it over the network for further processing.

Now in C# following code does the job in about 3 or 2 milliseconds.

Image image = Image.FromFile("D:/tst.jpg");
DateTime pre = DateTime.Now;
int sz;
using (MemoryStream sourceImageStream = new MemoryStream())
{
     image.Save(sourceImageStream, System.Drawing.Imaging.ImageFormat.Jpeg);
     byte[] sourceImageData = sourceImageStream.ToArray();
     sz = sourceImageData.Count();
}
MessageBox.Show("Size " + sz + " time : " + (DateTime.Now - pre).TotalMilliseconds);

Output:

Size 268152 time : 3.0118

But in Java doing the same as below takes way too much time.

BuffredImage image = ImageIO.read(new File("D:/tst.jpg"));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Instant pre = Instant.now();
ImageIO.write( image, "jpeg", baos );
baos.flush();
Instant now = Instant.now();
System.out.println("Size " + baos.size() + " time : " + ChronoUnit.MILLIS.between(pre, now));

Output:

Size 268167 time : 91.0

The source image is a JPG image. In C# when using png compressing. time was around 90ms. So my guess is that Java is taking time to somehow still compress the same JPG image. Image dimension is 2048 * 1536.

Java is frustratingly slow here. How can I get rid of this problem in Java?

Take this image into consideration.

C#:

Size 1987059 time : 11.0129

Java:

Size 845093 time : 155.0

The source image is 1987059 bytes (which is same as C# encoded byte array). But in Java it is compressed to 845093 bytes. I have tried setting the compression quality to 1f like this but it didn't help to reduce the time.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Sumsuddin Shojib
  • 3,583
  • 3
  • 26
  • 45
  • 1
    Do you know what micro benchmark effect is? see https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java –  Oct 11 '17 at 13:00
  • 3
    Of course Java compresses the image. Your `image` is not a JPEG, it's a `RenderedImage`. When you write it out in JPEG format, then you have a set of bytes representing a JPEG image. I suspect that the code is not at all same, and you're comparing apples to oranges. You're also assuming that the default settings (for JPEG encoding) are the same for both Java and C#. – Kayaman Oct 11 '17 at 13:07
  • 2
    You might want to initialize the `ByteArrayOutputStream` with more space than the default (`32`), so it doesn't need to be resized constantly. You can remove the `flush()` too. – Kayaman Oct 11 '17 at 13:12
  • edited the code to show that the same file is used in both cases. and the timer is counted only the important places. – Sumsuddin Shojib Oct 11 '17 at 13:13
  • Did you increase the initial size of `baos` like I said? – Kayaman Oct 11 '17 at 13:26
  • 2
    You can try setting ImageIO.setUseCache to false to prevent disk access. I'm not sure whether or not ByteArrayOutputStream will try to use the disk (or just memory). See https://docs.oracle.com/javase/7/docs/api/javax/imageio/ImageIO.html#setUseCache(boolean). – dsp_user Oct 11 '17 at 13:30
  • 1
    If you only want to transfer an image file over the network, just transfer the bytes of the original compressed image, as you would with any other binary file. *Do not involve image decoding/encoding.* Or, do you need to modify the image (pixel) data before transfer? In that case, your benchmark should also do that, to make sure you are comparing apples to apples. – Harald K Oct 11 '17 at 13:37
  • 1
    What haraldK said. Replace *all* of your code with `Files.copy(Paths.get("D:\\tst.jpg"), networkOutputStream)`. – VGR Oct 11 '17 at 13:56
  • sometimes I get `BufferedImage` from webcam and sometimes from processing other images. So I don't want to save it to any file and then transfer. If the whole image object could be converted to a byte array, that would help me a lot. And I don't want to send the pixels of the image. It will be slow and bandwidth hog. – Sumsuddin Shojib Oct 11 '17 at 16:35
  • Hi, @haraldK I have added an image so that we can say we are working with the same thing in both c# and java. Can you please look into that now? – Sumsuddin Shojib Oct 12 '17 at 06:05
  • `ImageIO.setUseCache(false)` didn't help either. – Sumsuddin Shojib Oct 12 '17 at 06:07
  • 1
    @Ultraviolet If a file input is not your real use case, don't use it in the benchmark. It doesn't matter if the source images are the same. Instead, use an in-memory generated image similar to what you would get from the webcam. This will ensure that you are actually exercising the JPEG encoder, not just copying the encoded bytes (this is what we have referred to as "apples and oranges" above). The .net `Image` clearly has some optimization that it doesn't re-encode an unchanged image in the same format. While a Java `BufferedImage` is just pixels, so a full re-encode always happens. – Harald K Oct 12 '17 at 07:45
  • @Ultraviolet ...or if you insist on using a file for input, use one in a different format (like PNG), to ensure you are exercising the encoder. – Harald K Oct 12 '17 at 07:46
  • Thanks, @haraldK, yes, C# is not re-encoding the same format. that why it's fast. And C# is also similarly faster on png encoding vs. `java`. So c# is somehow fast in this regard I have to say. – Sumsuddin Shojib Oct 12 '17 at 08:16
  • Yes, .net is faster at copying bytes than Java is at encoding a JPEG. But that is also not surprising, as the first task is trivial compared to the next. You can implement similar caching in Java if you like, but it seems like a waste of time, as this isn't your use case... – Harald K Oct 12 '17 at 08:21
  • Sorry, my `similarly faster` was confusing. I meant c# was faster there too (about 80 ms vs 600 ms). Don't you think `java` is unnecessarily slow here? – Sumsuddin Shojib Oct 12 '17 at 08:24

1 Answers1

2

The main problem with this kind of testing is pointed out in the first comment: This is a micro-benchmark. If you run that code only once in Java, you'll mostly measure the time taken to initialize the run-time, class loading and initialisatizion.

Here's a slightly modified version of your code (I originally wrote this as an answer to your follow-up question that is now closed as a duplicate, but the same concept applies), that at least includes a warm-up time. And you'll see that there's a quite a difference in the measurments. On my 2014 MacBook Pro, the output is:

Initial load time 415 ms (5)
Average warm up load time 73 ms (5)
Normal load time 65 ms (5)

As you see, the "normal" time to load an image, is a lot less than the initial time, which includes a lot of overhead.

Code:

public class TestJPEGSpeed {
    public static void main(String[] args) throws IOException {
        File input = new File(args[0]);

        test(input, 1, "Initial");
        test(input, 100, "Average warm up");
        test(input, 1, "Normal");
    }

    private static void test(File input, int runs, final String type) throws IOException {
        BufferedImage image = null;

        long start = System.currentTimeMillis();
        for (int i = 0; i < runs; i++) {
            image = ImageIO.read(input);
        }
        long stop = System.currentTimeMillis();

        System.out.println(type + " load time " + ((stop - start) / runs) + " ms (type=" + image.getType() + ")");
    }
}

(I also wrote a different version, that took a second parameter, and loaded a different file in the "normal" case, but the measurements were similar, so I left it out).

Most likely there's still issues with this benchmark, like measuring I/O time, rather than decoding time, but at least it's a little more fair.


PS: Some bonus background information. If you use an Oracle JRE at least, the bundled JPEG plugin for ImageIO uses JNI, and a native compiled version of IJG's libjpeg (written in C). This is used for both reading and writing JPEG. You could probably see better performance, if you used native bindings for libjpegTurbo. But as this is all native code, it's unlikely the performance will vary drastically from platform to platform.

Harald K
  • 26,314
  • 7
  • 65
  • 111
  • Thank you very much @haraldK. Last few day I was searching everything for a better solution and I have found about the same thing. I needed some educated benchmarks. Now, I am trying to tweak my library to get the byte[] directly and pass them to the server. https://github.com/sarxos/webcam-capture/blob/64fdd35d9f44f2866997771db6b380771c510a5c/webcam-capture-drivers/driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/impl/IpCamMJPEGStream.java#L132 – Sumsuddin Shojib Oct 16 '17 at 11:25