Por que eu iria ver ~ 20% de aumento de velocidade usando código nativo?

votos
24

Alguma idéia de por este código:

extern C __declspec(dllexport) void Transform(double x[], double y[], int iterations, bool forward)
{
    long n, i, i1, j, k, i2, l, l1, l2;
    double c1, c2, tx, ty, t1, t2, u1, u2, z;

    /* Calculate the number of points */
    n = (long)pow((double)2, (double)iterations);

    /* Do the bit reversal */
    i2 = n >> 1;
    j = 0;
    for (i = 0; i < n - 1; ++i)
    {
        if (i < j)
        {
            tx = x[i];
            ty = y[i];
            x[i] = x[j];
            y[i] = y[j];
            x[j] = tx;
            y[j] = ty;
        }
        k = i2;
        while (k <= j)
        {
            j -= k;
            k >>= 1;
        }
        j += k;
    }

    /* Compute the FFT */
    c1 = -1.0; 
    c2 = 0.0;
    l2 = 1;
    for (l = 0; l < iterations; ++l)
    {
        l1 = l2;
        l2 <<= 1;
        u1 = 1; 
        u2 = 0;
        for (j = 0; j < l1; j++) 
        {
            for (i = j; i < n; i += l2) 
            {
                i1 = i + l1;
                t1 = u1 * x[i1] - u2 * y[i1];
                t2 = u1 * y[i1] + u2 * x[i1];
                x[i1] = x[i] - t1; 
                y[i1] = y[i] - t2;
                x[i] += t1;
                y[i] += t2;
            }
            z = u1 * c1 - u2 * c2;
            u2 = u1 * c2 + u2 * c1;
            u1 = z;
        }
        c2 = sqrt((1.0 - c1) / 2.0);
        if (forward) 
            c2 = -c2;
        c1 = sqrt((1.0 + c1) / 2.0);
    }

    /* Scaling for forward transform */
    if (forward)
    {
        for (i = 0; i < n; ++i)
        {
            x[i] /= n;
            y[i] /= n;
        }
    }
}

corre 20% mais rápido do que este código?

public static void Transform(DataSet data, Direction direction)
{
    double[] x = data.Real;
    double[] y = data.Imag;
    data.Direction = direction;
    data.ExtremeImag = 0.0;
    data.ExtremeReal = 0.0;
    data.IndexExtremeImag = 0;
    data.IndexExtremeReal = 0;

    long n, i, i1, j, k, i2, l, l1, l2;
    double c1, c2, tx, ty, t1, t2, u1, u2, z;

    /* Calculate the number of points */
    n = (long)Math.Pow(2, data.Iterations);

    /* Do the bit reversal */
    i2 = n >> 1;
    j = 0;
    for (i = 0; i < n - 1; ++i)
    {
        if (i < j)
        {
            tx = x[i];
            ty = y[i];
            x[i] = x[j];
            y[i] = y[j];
            x[j] = tx;
            y[j] = ty;
        }
        k = i2;
        while (k <= j)
        {
            j -= k;
            k >>= 1;
        }
        j += k;
    }

    /* Compute the FFT */
    c1 = -1.0; 
    c2 = 0.0;
    l2 = 1;
    for (l = 0; l < data.Iterations; ++l)
    {
        l1 = l2;
        l2 <<= 1;
        u1 = 1; 
        u2 = 0;
        for (j = 0; j < l1; j++) 
        {
            for (i = j; i < n; i += l2) 
            {
                i1 = i + l1;
                t1 = u1 * x[i1] - u2 * y[i1];
                t2 = u1 * y[i1] + u2 * x[i1];
                x[i1] = x[i] - t1; 
                y[i1] = y[i] - t2;
                x[i] += t1;
                y[i] += t2;
            }
            z = u1 * c1 - u2 * c2;
            u2 = u1 * c2 + u2 * c1;
            u1 = z;
        }
        c2 = Math.Sqrt((1.0 - c1) / 2.0);
        if (direction == Direction.Forward) 
            c2 = -c2;
        c1 = Math.Sqrt((1.0 + c1) / 2.0);
    }

    /* Scaling for forward transform */
    if (direction == Direction.Forward)
    {
        for (i = 0; i < n; ++i)
        {
            x[i] /= n;
            y[i] /= n;
            if (Math.Abs(x[i]) > data.ExtremeReal)
            {
                data.ExtremeReal = x[i];
                data.IndexExtremeReal = (int)i;
            }
            if (Math.Abs(y[i]) > data.ExtremeImag)
            {
                data.ExtremeImag = y[i];
                data.IndexExtremeImag = (int)i;
            }
        }
    }
}

FFT http://www.rghware.com/fft.png

Eu criei a queda na CPU visto no meio do gráfico selecionando a opção “Native DLL FFT” em meu aplicativo:

http://www.rghware.com/InstrumentTuner.zip (código fonte)

Acho que isso vai funcionar na maioria dos PCs. Você precisa ter o DirectX instalado. Eu tive alguns problemas usando as configurações de captura para determinado hardware. As configurações de captura deveriam ser configurável, mas o desenvolvimento da aplicação foi desviado por este achado interessante.

De qualquer forma, por isso que eu estou vendo um aumento de 20% na velocidade usando o código nativo? Este parece voar em face de alguns dos pressupostos que eu tinha anteriormente.

ATUALIZAR

Depois de converter a função de um método inseguro e que fixa a longo emissão / int. O novo método inseguro realmente corre mais rápido do que o método nativo (muito legal).

Perfil http://www.rghware.com/profile.png

É óbvio que a verificação ligada matriz é a causa de 20% desacelerar neste método FFT. Devido à sua natureza, os para-loops em este método não pode ser otimizado.

Obrigado a todos pela ajuda.

Publicado 19/05/2009 em 17:04
fonte usuário
Em outras línguas...                            


7 respostas

votos
23

Basta olhar para este código, eu suspeitar da minha experiência uma desaceleração bastante significativo que vai de C ++ -> C #.

Uma questão importante que você vai enfrentar em uma porta ingênuo de uma rotina como esta para C # é que C # vai adicionar verificação de limites em cada cheque leque aqui. Desde que você nunca está looping através das matrizes de uma forma que vai ficar otimizado ( ver esta questão para mais detalhes ), apenas sobre cada acesso de matriz vai receber verificação de limites.

Além disso, esta porta é muito parecido com a> 1 mapeamento de C. 1- Se você executar este através de uma boa .NET profiler, você provavelmente vai encontrar alguns grandes manchas que podem ser otimizados para obter esta de volta para perto de C ++ velocidade com um ou dois ajustes (que tem sido quase sempre a minha experiência em portar rotinas como este).

Se você deseja obter este seja em velocidade quase idêntico, porém, você provavelmente vai precisar converter isso em código não seguro e usar a manipulação de ponteiros em vez de definir diretamente as matrizes. Isto irá eliminar todos os verificação de limites questões, e obter a sua velocidade para trás.


Edit: Eu vejo mais uma diferença enorme, que pode ser a razão do seu código inseguro C # está mais lento.

Confira esta página sobre C # comparação com C ++ , em particular:

"O tipo longo: Em C #, a longo tipo é de 64 bits, enquanto que no C ++, que é de 32 bits."

Você deve converter a versão C # para usar int, não muito tempo. Em C #, tempo é um tipo de 64 bits. Isso pode realmente ter um efeito profundo em sua manipulação de ponteiros, porque eu acredito que você está inadvertidamente adicionando um longo> conversão int (com verificação de estouro) em cada chamada ponteiro.

Além disso, enquanto você está nisso, você pode querer tentar envolver toda a função em um bloco desmarcada . C ++ não está fazendo a verificação de estouro que você está recebendo em C #.

Respondeu 19/05/2009 em 17:17
fonte usuário

votos
3

O compilador nativo pode fazer otimizações muito mais profundas e mais pesados ​​do que um compilador JIT, como vetorização, análise Interprocedimental, etc. E FFTs pode obter grandes speedups com vetorização.

Respondeu 19/05/2009 em 18:27
fonte usuário

votos
3

Isso é mais provável devido ao código de geração de compilador JIT que não é tão eficiente quanto o código gerado pelo compilador nativo.

Perfilar o código provavelmente deve ser seu próximo passo, se você se preocupa com a diminuição de 20% no desempenho, ou você pode considerar o uso de um fora da biblioteca prateleira otimizado.

Respondeu 19/05/2009 em 17:16
fonte usuário

votos
1

Eu corri o código que ele postou com int em vez de long e não realmente fazer a diferença. Eu sei que outras pessoas tiveram melhor sorte com FFT em .NET , mostrando que .NET pode alcançar ou exceder o proformance de C ++, mesmo com FFT matemática.

Assim, a minha resposta, código, quer do cartaz é mais otimizado (para C), em seguida, um no link, ou é menos otimizado para C # do que o do artigo I ligada.

I realizados dois conjuntos de ensaios em duas máquinas com .NET 2.0. Uma máquina tinha XPSP2 e tinha um único processador, 850 MHz Pentium III, com 512 Mb de RAM. A outra máquina tinha construir 5321 de Vista e tinha um único processador, 2 GHz Pentium Mobile 4, com 1GB de RAM. Em cada caso, calculou-se a média de 100 cálculos FFT separadas em valores de 217 (131072) de dados. A partir desses valores que eu calculou o erro padrão a partir do desvio padrão.

Os resultados são mostrados em ms. Os resultados para a máquina Pentium III são:

  Not Optimized   Optimized For Space Optimized For Speed
Unmanaged   92.88 ± 0.09    88.23 ± 0.09    68.48 ± 0.03
Managed C++ 72.89 ± 0.03    72.26 ± 0.04    71.35 ± 0.06
C++/CLI 73.00 ± 0.05    72.32 ± 0.03    71.44 ± 0.04
C# Managed  72.21 ± 0.04    69.97 ± 0.08

Os resultados para o Pentium Mobile 4 são:

          Not Optimized   Optimized For Space Optimized For Speed
Unmanaged   45.2 ± 0.1  30.04 ± 0.04    23.06 ± 0.04
Managed C++ 23.5 ± 0.1  23.17 ± 0.08    23.36 ± 0.07
C++/CLI 23.5 ± 0.1  23.11 ± 0.07    23.80 ± 0.05
C# Managed  23.7 ± 0.1  22.78 ± 0.03
Respondeu 20/05/2009 em 06:34
fonte usuário

votos
1

Considerando que o código gerenciado faz verificação de limites sobre o índice de cada acesso à matriz, que o código não gerenciado não faz, eu diria que a diferença é menor do que eu esperava.

Se você alterar as matrizes para ponteiros no código gerenciado também (que é o que eles realmente estão no código não gerenciado), eu esperaria que eles realizem sobre o mesmo.

Respondeu 19/05/2009 em 17:19
fonte usuário

votos
0

Porque compilador C # .NET não é a melhor em produzir código eficiente. E toda a lógica da linguagem impede isso. BTW, F # tem um desempenho muito melhor do que C # in Math

Respondeu 13/06/2009 em 12:39
fonte usuário

votos
0

Você usou um profiler como AQTime para ver onde o gargalo é? Às vezes é uma coisa trivial ao traduzir nativo para código gerenciado. Por outro lado, porque o código gerenciado em alguns cenários é mais lento do que o código nativo, você pode querer tentar código inseguro em seu lugar.

Respondeu 19/05/2009 em 17:08
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more