Небезопасная итерация и растровая карта - почему Uint64 быстрее?

StackOverflow https://stackoverflow.com/questions/5812990

Вопрос

Я выполнял некоторые небезопасные операции растрового изображения и обнаружил, что увеличение указателя меньше раз может привести к некоторым улучшениям производительности. Я не уверен, почему это так, даже если вы делаете гораздо более бить в курсе, все равно лучше делать меньше итераций на указателе.

Так, например, вместо того, чтобы итерация более 32 -битных пикселей с помощью UINT32, более двух пикселей с UINT64, и выполняйте два раза больше операций за один цикл.

Следующее делает это, прочитав два пикселя и изменяя их (конечно, оно потерпит неудачу с изображениями с нечетной шириной, но это только для тестирования).

    private void removeBlueWithTwoPixelIteration()
    {
        // think of a big image with data
        Bitmap bmp = new Bitmap(15000, 15000, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
        TimeSpan startTime, endTime;

        unsafe {

            UInt64 doublePixel;
            UInt32 pixel1;
            UInt32 pixel2;

            const int readSize = sizeof(UInt64);
            const UInt64 rightHalf = UInt32.MaxValue;

            PerformanceCounter pf = new PerformanceCounter("System", "System Up Time"); pf.NextValue();

            BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
            byte* image = (byte*)bd.Scan0.ToPointer();

            startTime = TimeSpan.FromSeconds(pf.NextValue());

            for (byte* line = image; line < image + bd.Stride * bd.Height; line += bd.Stride)
            {
                for (var pointer = line; pointer < line + bd.Stride; pointer += readSize)
                {
                    doublePixel = *((UInt64*)pointer);
                    pixel1 = (UInt32)(doublePixel >> (readSize * 8 / 2)) >> 8; // loose last 8 bits (Blue color)
                    pixel2 = (UInt32)(doublePixel & rightHalf) >> 8; // loose last 8 bits (Blue color)
                    *((UInt32*)pointer) = pixel1 << 8; // putback but shift so A R G get back to original positions
                    *((UInt32*)pointer + 1) = pixel2 << 8; // putback but shift so A R G get back to original positions
                }
            }

            endTime = TimeSpan.FromSeconds(pf.NextValue());

            bmp.UnlockBits(bd);
            bmp.Dispose();

        }

        MessageBox.Show((endTime - startTime).TotalMilliseconds.ToString());

    }

В следующем коде это пиксель от Pixel и Примерно на 70% медленнее чем предыдущий:

    private void removeBlueWithSinglePixelIteration()
    {
        // think of a big image with data
        Bitmap bmp = new Bitmap(15000, 15000, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
        TimeSpan startTime, endTime;

        unsafe
        {

            UInt32 singlePixel;

            const int readSize = sizeof(UInt32);

            PerformanceCounter pf = new PerformanceCounter("System", "System Up Time"); pf.NextValue();

            BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
            byte* image = (byte*)bd.Scan0.ToPointer();

            startTime = TimeSpan.FromSeconds(pf.NextValue());

            for (byte* line = image; line < image + bd.Stride * bd.Height; line += bd.Stride)
            {
                for (var pointer = line; pointer < line + bd.Stride; pointer += readSize)
                {
                    singlePixel = *((UInt32*)pointer) >> 8; // loose B
                    *((UInt32*)pointer) = singlePixel << 8; // adjust A R G back
                }
            }

            endTime = TimeSpan.FromSeconds(pf.NextValue());

            bmp.UnlockBits(bd);
            bmp.Dispose();

        }

        MessageBox.Show((endTime - startTime).TotalMilliseconds.ToString());
    }

Может ли кто -нибудь уточнить, почему увеличивает указатель более дорогостоящей операцией, чем выполнение нескольких побитовых операций?

Я использую .NET 4 Framework.

Может ли что -то подобное быть правдой для C ++?

Нб. 32 -битный против 64 -битный соотношение двух методов равны, однако оба способа примерно на 20% медленнее на 64 против 32 бит?

РЕДАКТИРОВАТЬ: Как предложено Porges и Arul, это может быть из -за уменьшения числа чтений памяти и ветвления накладных расходов.

Edit2:

После некоторого тестирования кажется, что чтение из памяти меньше времени - это ответ:

С этим кодом, предполагая, что ширина изображения делится на 5, вы получаете на 400% быстрее:

[StructLayout(LayoutKind.Sequential,Pack = 1)]
struct PixelContainer {
    public UInt32 pixel1;
    public UInt32 pixel2;
    public UInt32 pixel3;
    public UInt32 pixel4;
    public UInt32 pixel5;
}

Затем используйте это:

            int readSize = sizeof(PixelContainer);

            // .....

            for (var pointer = line; pointer < line + bd.Stride; pointer += readSize)
            {
                multiPixel = *((PixelContainer*)pointer);
                multiPixel.pixel1 &= 0xFFFFFF00u;
                multiPixel.pixel2 &= 0xFFFFFF00u;
                multiPixel.pixel3 &= 0xFFFFFF00u;
                multiPixel.pixel4 &= 0xFFFFFF00u;
                multiPixel.pixel5 &= 0xFFFFFF00u;
                *((PixelContainer*)pointer) = multiPixel;
            }
Это было полезно?

Решение

Это техника, известная как петля развертывания. Основная выгода для производительности должна быть связана с уменьшением ветвления накладных расходов.

В качестве примечания, вы можете немного ускорить его, используя Bitmask:

*((UInt64 *)pointer) &= 0xFFFFFF00FFFFFF00ul;

Другие советы

Это не увеличение указателя, которое медленнее, а чтение из памяти. С 32-битными единицами вы делаете вдвое больше чтений.

Вы должны найти его снова быстрее, если вы напишите один раз вместо дважды в 64-битной версии.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top