Skip to content
This repository has been archived by the owner on Dec 31, 2022. It is now read-only.

Improvment of the interface #3

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

QSOlePettersson
Copy link

The decompress interface did not allow getting the necessary information from the JPEG header to determine which parameters to use for the decompression call. As far as I can tell, the JPEG file format supports either 8 bit or 24 bit images. To be able to load an image file "as is", one must be able to determine which of these cases are to be applied for the current image file.
Furthermore, I oppose the usage of the "unsafe" keyword on principle and mapping of managed memory directly to use in an unmanaged context and have therefore refactored the necessary parts.
With the changes from this fork, it is now possible to simply decompress a JPEG bytestream (without conversion) to a System.Drawing.Bitmap, as well as querying the format of the JPEG stream and allocating a suitable Bitmap object (for example from a pool for memory conservation) and decompress to it.

Added wrapper class for unmanaged memory allocated by native tj-functions.
Added Compress overload to allow skipping marshalling to managed space.
Added static grayscale palette for TJDecompressor.
Added overloads for Decompress to allow decoding an image without specifying a pixelformat.
Added overloads for GetImageInfo to get actual pixelformat from image header before decoding.
@qmfrederik
Copy link
Collaborator

Thanks! The changes related to fetch the JPEG parameters such as bit depth look good to me.

Unfortunately, I believe I can't take the change related to the unsafe keyword as is. I think it introduces additional allocation and copying of memory. We use the TurboJpegWrapper in a tight loop to decompress a feed of JPEG images and this really impacts performance. I'll comment on the code.
That's one of the reasons we sometimes use unsafe code.

Would you mind splitting this PR in two?


fixed (byte* bufPtr = buf)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this change introduces a performance regression.

In the original code, one buffer (buf) was allocated in managed code which was pinned and shared with unmanaged code.

In the changed code, you first create a new buffer in unmanaged code (new TJUnmanagedMemory(bufferSize)).
When you return outBuf, you implicitly cast it to a byte[]. That operator allocates a new byte array and returns that one.

Net, in the original code you had one allocation of a large object, in the changed code you have two allocations (one in unmanaged memory and one in managed memory); plus a copy operation.

This code is on a hot path for us so performance is important.

Perhaps using Span<T> may be a solution here, not sure.
If you think there's no performance difference, feel free to send over a BenchmarkDotNet test which compares both ;-).

Copy link
Collaborator

@qmfrederik qmfrederik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would need to better understand the performance impact of removing the unsafe keyword.

The JPEG-related changes look good; feel free to split them off in a separate PR.

Thanks!

@QSOlePettersson
Copy link
Author

QSOlePettersson commented Dec 11, 2017 via email

@qmfrederik
Copy link
Collaborator

In my case, the compressed images contain the framebuffers of Android devices, so resolutions of 1920x1200 at 32-bit pixel depth are not uncommon.
We decompress the images, calculate the delta, and then send incremental updates to clients.
We want to get at least 20fps, so we need to do this 20 times per second (more is better).

So in this example, we'd be allocating and copying an additional 320MB per second.
You notice that in CPU usage and garbage collection statistics ;-).

We don't interop with GDI+ in this case so our scenario is indeed different from yours. If you want to pass the unmanaged memory to GDI you can optimize differently.

I don't mind taking changes that optimize for a specific scenario; just want avoid a regression for other scenarios :-).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants