Pergunta

Eu tenho um método que precisa ser tão rápido quanto ele puder, ele usa ponteiros de memória inseguras e sua minha primeira incursão neste tipo de codificação, então eu sei que provavelmente pode ser mais rápido.

    /// <summary>
    /// Copies bitmapdata from one bitmap to another at a specified point on the output bitmapdata
    /// </summary>
    /// <param name="sourcebtmpdata">The sourcebitmap must be smaller that the destbitmap</param>
    /// <param name="destbtmpdata"></param>
    /// <param name="point">The point on the destination bitmap to draw at</param>
    private static unsafe void CopyBitmapToDest(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
    {
        // calculate total number of rows to draw.
        var totalRow = Math.Min(
            destbtmpdata.Height - point.Y,
            sourcebtmpdata.Height);


        //loop through each row on the source bitmap and get mem pointers
        //to the source bitmap and dest bitmap
        for (int i = 0; i < totalRow; i++)
        {
            int destRow = point.Y + i;

            //get the pointer to the start of the current pixel "row" on the output image
            byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride);
            //get the pointer to the start of the FIRST pixel row on the source image
            byte* srcRowPtr = (byte*)sourcebtmpdata.Scan0 + (i * sourcebtmpdata.Stride);

            int pointX = point.X;
            //the rowSize is pre-computed before the loop to improve performance
            int rowSize = Math.Min(destbtmpdata.Width - pointX, sourcebtmpdata.Width);
            //for each row each set each pixel
            for (int j = 0; j < rowSize; j++)
            {
                int firstBlueByte = ((pointX + j)*3);

                int srcByte = j *3;
                destRowPtr[(firstBlueByte)] = srcRowPtr[srcByte];
                destRowPtr[(firstBlueByte) + 1] = srcRowPtr[srcByte + 1];
                destRowPtr[(firstBlueByte) + 2] = srcRowPtr[srcByte + 2];
            }


        }
    }

Então, há algo que pode ser feito para tornar isso mais rápido? Ignore o TODO, por agora, mal correção que mais tarde uma vez que tenho algumas medições de desempenho de linha de base.

UPDATE:. Desculpe, deveria ter mencionado que a razão pela qual eu estou usando isso em vez de Graphics.DrawImage é porque im implementação de multi-threading e por isso eu não posso usar DrawImage

UPDATE 2:. Estou ainda não satisfeito com o desempenho e eu tenho certeza que há mais algumas ms que pode ser tido

Foi útil?

Solução

Havia algo fundamentalmente errado com o código que eu não posso acreditar que eu não notei até agora.

byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride);

Este recebe um ponteiro para a linha de destino, mas não obter a coluna que ele está copiando para, que no código antigo é feita dentro do loop RowSize. Ele agora se parece com:

byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride) + pointX * 3;

Então, agora temos o ponteiro correto para os dados de destino. Agora podemos nos livrar disso para loop. Usando sugestões de Vilx- e Rob o código agora se parece com:

        private static unsafe void CopyBitmapToDestSuperFast(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
    {
        //calculate total number of rows to copy.
        //using ternary operator instead of Math.Min, few ms faster
        int totalRows = (destbtmpdata.Height - point.Y < sourcebtmpdata.Height) ? destbtmpdata.Height - point.Y : sourcebtmpdata.Height;
        //calculate the width of the image to draw, this cuts off the image
        //if it goes past the width of the destination image
        int rowWidth = (destbtmpdata.Width - point.X < sourcebtmpdata.Width) ? destbtmpdata.Width - point.X : sourcebtmpdata.Width;

        //loop through each row on the source bitmap and get mem pointers
        //to the source bitmap and dest bitmap
        for (int i = 0; i < totalRows; i++)
        {
            int destRow = point.Y + i;

            //get the pointer to the start of the current pixel "row" and column on the output image
            byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride) + point.X * 3;

            //get the pointer to the start of the FIRST pixel row on the source image
            byte* srcRowPtr = (byte*)sourcebtmpdata.Scan0 + (i * sourcebtmpdata.Stride);

            //RtlMoveMemory function
            CopyMemory(new IntPtr(destRowPtr), new IntPtr(srcRowPtr), (uint)rowWidth * 3);

        }
    }

Copiar uma imagem de 500x500 para uma imagem de 5000x5000 em uma grade de 50 vezes levou: 00: 00: 07.9948993 segundos. Agora, com as alterações acima leva 00: 00: 01.8714263 segundos. Muito melhor.

Outras dicas

Bem ... Eu não tenho certeza se .NET bitmap formatos de dados são inteiramente compatível com as funções GDI32 do windows ...

Mas um dos primeiros Win32 API que eu aprendi foi BitBlt:

BOOL BitBlt(
  HDC hdcDest, 
  int nXDest, 
  int nYDest, 
  int nWidth, 
  int nHeight, 
  HDC hdcSrc, 
  int nXSrc, 
  int nYSrc, 
  DWORD dwRop
);

E foi a mais rápido forma de copiar os dados ao redor, se bem me lembro.

Aqui está a assinatura BitBlt PInvoke para uso em C # e informações de uso relacionadas, uma ótima leitura para qualquer um trabalhando com gráficos de alto desempenho em C #:

olhar Definitivamente a.

O loop interno é onde você quer concentrar-se muito do seu tempo (mas, fazer medidas para garantir que)

for  (int j = 0; j < sourcebtmpdata.Width; j++)
{
    destRowPtr[(point.X + j) * 3] = srcRowPtr[j * 3];
    destRowPtr[((point.X + j) * 3) + 1] = srcRowPtr[(j * 3) + 1];
    destRowPtr[((point.X + j) * 3) + 2] = srcRowPtr[(j * 3) + 2];
}
  1. se livrar do multiplica e a indexação de matriz (que é uma multiplicação sob os capuzes) e substituir por um ponteiro que você está incrementando.

  2. Ditto com o +1, +2, incrementar um ponteiro.

  3. Provavelmente o seu compilador não vai manter computação point.X (cheque), mas fazer uma variável local apenas no caso. Ele não vai fazê-lo na única iteração, mas talvez cada iteração.

Você pode querer olhar em Eigen .

É uma biblioteca C ++ modelo que usa SSE (2 e mais tarde) e conjuntos de instruções AltiVec com fallback graciosa para não-vetorizadas código .

Rápido. (Veja benchmark).
modelos de expressão permite remover de forma inteligente temporários e permitir avaliação preguiçosa, quando isso é apropriado - Eigen cuida disso automaticamente e alças aliasing também na maioria dos casos
. vetorização explícita é realizada para o SSE (2 e mais tarde) e conjuntos de instruções AltiVec, com fallback graciosa para código não-vectorized. modelos de expressão permitem realizar essas otimizações globalmente para expressões inteiras.
Com objetos de tamanho fixo, alocação dinâmica de memória é evitado, e os loops são desenrolou quando isso faz sentido.
Para grandes matrizes, é dada especial atenção para o cache de uso.

Você poderia implementar você funciona em C ++ e, em seguida, chamar isso de C #

Você nem sempre precisa usar ponteiros para conseguir uma boa velocidade. Este deve ser dentro de alguns ms do original:

        private static void CopyBitmapToDest(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
    {
        byte[] src = new byte[sourcebtmpdata.Height * sourcebtmpdata.Width * 3];
        int maximum = src.Length;
        byte[] dest = new byte[maximum];
        Marshal.Copy(sourcebtmpdata.Scan0, src, 0, src.Length);
        int pointX = point.X * 3;
        int copyLength = destbtmpdata.Width*3 - pointX;
        int k = pointX + point.Y * sourcebtmpdata.Stride;
        int rowWidth = sourcebtmpdata.Stride;
        while (k<maximum)
        {
            Array.Copy(src,k,dest,k,copyLength);
            k += rowWidth;

        }
        Marshal.Copy(dest, 0, destbtmpdata.Scan0, dest.Length);
    }

Infelizmente eu não tenho o tempo para escrever uma solução completa, mas eu gostaria de olhar em usar a plataforma RtlMoveMemory () função para mover linhas como um todo, não byte a byte. Isso deve ser muito mais rápido.

Eu acho que os limites de número de tamanho do passo e linha pode ser calculada com antecedência.

E eu precalculated todas as multiplicações, resultando no código a seguir:

private static unsafe void CopyBitmapToDest(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
{
    //TODO: It is expected that the bitmap PixelFormat is Format24bppRgb but this could change in the future
    const int pixelSize = 3;

    // calculate total number of rows to draw.
    var totalRow = Math.Min(
        destbtmpdata.Height - point.Y,
        sourcebtmpdata.Height);

    var rowSize = Math.Min(
        (destbtmpdata.Width - point.X) * pixelSize,
        sourcebtmpdata.Width * pixelSize);

    // starting point of copy operation
    byte* srcPtr = (byte*)sourcebtmpdata.Scan0;
    byte* destPtr = (byte*)destbtmpdata.Scan0 + point.Y * destbtmpdata.Stride;

    // loop through each row
    for (int i = 0; i < totalRow; i++) {

        // draw the entire row
        for (int j = 0; j < rowSize; j++)
            destPtr[point.X + j] = srcPtr[j];

        // advance each pointer by 1 row
        destPtr += destbtmpdata.Stride;
        srcPtr += sourcebtmpdata.Stride;
    }

}

Havn't testado-lo completamente, mas você deve ser capaz de chegar a esse trabalho.

removi multiplicação operações do laço (pré-calculada vez) e removido a maioria das ramificações por isso deve ser um pouco mais rapidamente.

Deixe-me saber se isso ajuda: -)

Estou olhando para o código C # e eu não posso reconhecer algo familiar. Tudo parece como uma tonelada de C ++. BTW, parece que DirectX / XNA precisa para se tornar seu novo amigo. Apenas meus 2 centavos. Não mate o mensageiro.

Se você deve contar com CPU para fazer isso: Eu tenho feito algumas otimizações de layout de 24 bits mim, e posso dizer-lhe que a velocidade de acesso à memória deve ser o gargalo. instruções Use SSE3 para mais rápido acesso bytewise possível. Este meio C ++ e incorporado linguagem assembly. Em puro C você vai ser 30% mais lento na maioria das máquinas.

Tenha em mente que GPUs modernas são muito mais rápidos do que o CPU neste tipo de operações.

Eu não tenho certeza se isso vai proporcionar um desempenho extra, mas eu ver o padrão muito no refletor.

Assim:

int srcByte = j *3;
destRowPtr[(firstBlueByte)] = srcRowPtr[srcByte];
destRowPtr[(firstBlueByte) + 1] = srcRowPtr[srcByte + 1];
destRowPtr[(firstBlueByte) + 2] = srcRowPtr[srcByte + 2];

Torna-se:

*destRowPtr++ = *srcRowPtr++;
*destRowPtr++ = *srcRowPtr++;
*destRowPtr++ = *srcRowPtr++;

precisa provavelmente mais chaves.

Se a largura é fixo, você provavelmente poderia desenrolar toda a linha em algumas centenas de linhas. :)

Atualizar

Você também pode tentar utilizar um tipo maior, por exemplo, Int32 ou Int64 para um melhor desempenho.

Tudo bem, isso vai ser bastante perto da linha de quantas ms você pode sair do algoritmo, mas se livrar da chamada para Math.min e substituí-lo com um operador trinário vez.

Geralmente, fazer uma chamada biblioteca vai demorar mais tempo do que fazer algo em seu próprio país e eu fiz um piloto de testes simples para confirmar isso por Math.min.

using System;
using System.Diagnostics;

namespace TestDriver
{
    class Program
    {
        static void Main(string[] args)
        {
            // Start the stopwatch
            if (Stopwatch.IsHighResolution)
            { Console.WriteLine("Using high resolution timer"); }
            else
            { Console.WriteLine("High resolution timer unavailable"); }
            // Test Math.Min for 10000 iterations
            Stopwatch sw = Stopwatch.StartNew();
            for (int ndx = 0; ndx < 10000; ndx++)
            {
                int result = Math.Min(ndx, 5000);
            }
            Console.WriteLine(sw.Elapsed.TotalMilliseconds.ToString("0.0000"));
            // Test trinary operator for 10000 iterations
            sw = Stopwatch.StartNew();
            for (int ndx = 0; ndx < 10000; ndx++)
            {
                int result = (ndx < 5000) ? ndx : 5000;
            }
            Console.WriteLine(sw.Elapsed.TotalMilliseconds.ToString("0.0000"));
            Console.ReadKey();
        }
    }
}

Os resultados ao executar o acima no meu computador, um processador Intel T2400 @ 1.83GHz. Também, notar que existe um bit de variação nos resultados, mas geralmente o operador Trinay é mais rápido por cerca de 0,01 ms. Isso não é muito, mas ao longo de um conjunto de dados grande o suficiente ele vai somar.

Usando alta resolução temporizador
0,0539
0,0402

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top