Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster copy_from implementation #2417

Open
Jasper-Bekkers opened this issue Feb 5, 2025 · 0 comments
Open

Faster copy_from implementation #2417

Jasper-Bekkers opened this issue Feb 5, 2025 · 0 comments

Comments

@Jasper-Bekkers
Copy link

After #909 w/ the move to remove unsafe code, I think (didn't test extensively) the implementation of copy_from got significantly slower. At the very least, it became a recent bottleneck for me that I spent some time looking at.

The implementation of copy_from is a pretty straight forward get_pixel/put_pixel loop over the image data where the function calls aren't inlined and are performing bounds checks on every access, even tho the preconditions of the function already do the bounds check beforehand.

In my specific case I managed to ~3x increase the performance, without re-introducing unsafe code, by copying over scanlines at a time. The code below lives in my project because I can live without it having to be a generalized implementation (as you see - it would need to be adapted to GenericImage/GenericImageView to be able to live in this project properly).

I'm submitting it here (rather then as a pull request) because I found a way to entirely not go down this code path at all anymore, however, I'm sharing it because I think it's useful to this project still and can potentially be adapted.

trait FastCopyFrom {
    type Pixel: image::Pixel;

    fn fast_copy_from(&mut self, other: &image::RgbaImage, x: u32, y: u32);
}

impl FastCopyFrom for image::RgbaImage {
    type Pixel = <image::RgbaImage as GenericImageView>::Pixel;
    fn fast_copy_from(&mut self, other: &image::RgbaImage, x: u32, y: u32) {
        // Do bounds checking here so we can use the non-bounds-checking
        // functions to copy pixels.
        if self.width() < other.width() + x || self.height() < other.height() + y {
            return;
        }

        let other_data = other.as_raw();
        let self_w = self.width();

        let mut self_data = self.as_flat_samples_mut();
        let mut self_slice = self_data.as_mut_slice();
        let other_w = other.width();

        for k in 0..other.height() {
            let other_start = ((k * other_w) * 4) as usize;
            let other_end = (((k + 1) * other_w) * 4) as usize;

            let self_start = (((x) + ((k + y) * self_w)) * 4) as usize;
            let self_end = self_start + (other_w * 4) as usize;

            self_slice[self_start..self_end].copy_from_slice(
                &other_data[other_start..other_end]
            );
        }
    }
}

Timings before

Image

Timings after

Image

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

No branches or pull requests

1 participant