Производительность C# LockBits (от int[,] до byte[])

Graphics g;
using (var bmp = new Bitmap(_frame, _height, PixelFormat.Format24bppRgb))
{
    var data = bmp.LockBits(new Rectangle(0, 0, _frame, _height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
    var bmpWidth = data.Stride;
    var bytes = bmpWidth * _height;
    var rgb = new byte[bytes];
    var ptr = data.Scan0;
    Marshal.Copy(ptr, rgb, 0, bytes);

    for (var i = 0; i < _frame; i++)
    {
        var i3 = (i << 1) + i;
        for (var j = 0; j < _height; j++)
        {
            var ij = j * bmpWidth + i3;
            var val = (byte)(_values[i, j]);
            rgb[ij] = val;
            rgb[ij + 1] = val;
            rgb[ij + 2] = val;
        }
    }

    Marshal.Copy(rgb, 0, ptr, bytes);
    bmp.UnlockBits(data);

    g = _box.CreateGraphics();
    g.InterpolationMode = InterpolationMode.NearestNeighbor;
    g.DrawImage(bmp, 0, 0, _box.Width, _box.Height);
}
g.Dispose();

Я использую этот код для преобразования массива значений RGB (оттенки серого) в PictureBox, но он медленный. Пожалуйста, скажите мне мои ошибки. На данный момент массив из 441 000 элементов обрабатывается за 35 мс. Мне нужно обрабатывать массив из 4 миллионов за то же время.


person Kir    schedule 23.09.2011    source источник
comment
Большая часть времени уходит на преобразование или на остальную часть кода (распределение, блокировку, разблокировку)?   -  person user541686    schedule 24.09.2011
comment
Большую часть времени в двух вложенных циклах (~32 мс). Возможно, это связано с приведением in к byte. Но я незнаю как это решить оптимально.   -  person Kir    schedule 24.09.2011
comment
Вы делаете эти тайминги в отладчике? Если так, то они совсем не надежны. Запустите тайминги в релизном режиме, не подключая отладчик (для запуска Ctrl+F5).   -  person Jim Mischel    schedule 24.09.2011
comment
Что показывает запуск теста производительности?   -  person    schedule 24.09.2011


Ответы (5)


Вы можете пропустить первый Array.Copy, где вы копируете данные из изображения в массив, так как вы все равно перезапишете все данные в массиве.

Это сократит примерно 25% времени, но если вы хотите, чтобы это было быстрее, вам придется использовать небезопасный блок кода, чтобы вы могли использовать указатели. Таким образом, вы можете обойти проверку диапазона при доступе к массивам и можете записывать данные непосредственно в данные изображения, а не копировать их.

person Guffa    schedule 23.09.2011

Я полностью согласен с ответом Гуффы. Использование небезопасного блока кода ускорит процесс. Чтобы еще больше повысить производительность, вы можете выполнять цикл for параллельно, используя класс Parallel в среде .Net. Для больших растровых изображений это повышает производительность. Вот небольшой пример кода:

using (Bitmap bmp = (Bitmap)Image.FromFile(@"mybitmap.bmp"))
{
  int width = bmp.Width;
  int height = bmp.Height;

  BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height),
    System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

  byte* s0 = (byte*)bd.Scan0.ToPointer();
  int stride = bd.Stride;

  Parallel.For(0, height, (y1) =>
  {
    int posY = y1*stride;
    byte* cpp = s0 + posY;

    for (int x = 0; x < width; x++)
    {              
      // Set your pixel values here.
      cpp[0] = 255;
      cpp[1] = 255;
      cpp[2] = 255;
      cpp += 3;
    }
  });

  bmp.UnlockBits(bd);
}

Чтобы не усложнять пример, я установил значения пикселей в постоянное значение. Обратите внимание: чтобы скомпилировать приведенный выше пример, вы должны разрешить небезопасный код.

Надеюсь это поможет.

person Hans    schedule 24.09.2011

В дополнение к отличному совету Guffa, я бы посоветовал вам профилировать свой код, чтобы увидеть, на что он тратит время. Убедитесь, что когда вы отсчитываете это время, вы работаете в режиме выпуска без подключенного отладчика.

Я не удивлюсь, если вызов DrawImage будет занимать большую часть времени. Вы масштабируете изображение там, что может быть довольно дорого. Насколько велика коробка, в которую вы рисуете изображение?

Наконец, хотя это не повлияет на производительность, вы должны изменить свой код следующим образом:

using (Graphics g = _box.CreateGraphics())
{
    g.InterpolationMode = InterpolationMode.NearestNeighbor;
    g.DrawImage(bmp, 0, 0, _box.Width, _box.Height);
}

И избавьтесь от первой и последней строк в вашем примере.

person Jim Mischel    schedule 23.09.2011

Попробуйте это, используя небезопасный код:

byte* rp0;
int* vp0;
fixed (byte* rp1 = rgb)
{
    rp0 = rp1;
    fixed (int* vp1 = _values)
    {
        vp0 = vp1;
        Parallel.For(0, _width, (i) =>
        {
            var val = (byte)vp0[i];
            rp0[i] = val;
            rp0[i + 1] = val;
            rp0[i + 2] = val;
        });
    }
}

Бегает очень быстро для меня

person Seph    schedule 25.09.2011

Насколько я понимаю, многомерные (квадратные) массивы в .Net работают довольно медленно. Вместо этого вы можете попробовать изменить массив _values ​​на одномерный массив. Вот одна ссылка, если поискать, можно найти и другие: http://odetocode.com/articles/253.aspx

Пример производительности массива.

using System;
using System.Diagnostics;

class Program
{
static void Main(string[] args)
{
    int w = 1000;
    int h = 1000;

    int c = 1000;

    TestL(w, h);
    TestM(w, h);


    var swl = Stopwatch.StartNew();
    for (int i = 0; i < c; i++)
    {
        TestL(w, h);
    }
    swl.Stop();

    var swm = Stopwatch.StartNew();
    for (int i = 0; i < c; i++)
    {
        TestM(w, h);
    }
    swm.Stop();

    Console.WriteLine(swl.Elapsed);
    Console.WriteLine(swm.Elapsed);
    Console.ReadLine();
}


static void TestL(int w, int h)
{
    byte[] b = new byte[w * h];
    int q = 0;
    for (int x = 0; x < w; x++)
        for (int y = 0; y < h; y++)
            b[q++] = 1;
}

static void TestM(int w, int h)
{
    byte[,] b = new byte[w, h];

    for (int y = 0; y < h; y++)
        for (int x = 0; x < w; x++)
            b[y, x] = 1;
}
}
person MarkPflug    schedule 23.09.2011
comment
Под довольно медленным я подразумеваю более медленный, чем обычный доступ к массиву. - person MarkPflug; 24.09.2011
comment
Это понимание основано на .NET 1.x. Многомерные массивы в .NET 2.0 и более поздних версиях работают довольно быстро. - person Jim Mischel; 24.09.2011
comment
@Jim: Если это так, пожалуйста, измените пример кода, который я только что добавил, и сделайте его таким, чтобы тайминги были примерно равными. Потому что на моей машине одномерный массив значительно быстрее. Просто установите все значения на 1, например. Спасибо! Я укажу, что это верно только при компиляции x86. С x64 тайминги равны. - person MarkPflug; 24.09.2011
comment
Я исправляюсь. Я неправильно прочитал ваше утверждение и подумал о разнице между доступом к многомерному массиву [,] и доступом к неровному массиву [][]. Вы правы, доступ к одномерному массиву будет быстрее. - person Jim Mischel; 24.09.2011
comment
Что еще более странно, так это то, что в 64-битном режиме, если вы делаете массивы static, метод TestM медленнее, чем версия, которая каждый раз выделяет новый массив. При этом метод TestL работает примерно на 50% быстрее. Действительно странно. - person Jim Mischel; 24.09.2011