SSE divisão inteira?

Há _mm_div_ps para divisão de valores de ponto flutuante, há _mm_mullo_epi16 para multiplicação de inteiro. Mas existe algo para divisão inteira (valor de 16 bits)? Como posso conduzir essa divisão?

Por favor, veja a class vetorial de Agner Fog que ele implementou um algoritmo rápido para fazer a divisão inteira com SSE / AVX para palavras de 8 bits, 16 bits e 32 bits (mas não 64 bits) http://www.agner.org/ otimizar / # vectorclass

Olhe no arquivo vectori128.h para o código e uma descrição do algoirthm como seu manual bem escrito VectorClass.pdf

Aqui está um fragment descrevendo o algoritmo de seu manual.

“Integer division Não há instruções no conjunto de instruções x86 e suas extensões que são úteis para a divisão de vetores inteiros, e tais instruções seriam bastante lentas se elas existissem. Portanto, a biblioteca de classs de vetor está usando um algoritmo para divisão de inteiros rápidos. O princípio básico deste algoritmo pode ser expresso nesta fórmula: a / b ≈ a * (2n / b) >> n Este cálculo segue os seguintes passos: 1. encontre um valor adequado para n 2. calcule 2n / b 3. calcular correções necessárias para erros de arredondamento 4. fazer a multiplicação e shift-right e aplicar correções para erros de arredondamento

Esta fórmula é vantajosa se vários números forem divididos pelo mesmo divisor b. Os passos 1, 2 e 3 só precisam ser feitos uma vez enquanto o passo 4 é repetido para cada valor do dividendo a. Os detalhes matemáticos são descritos no arquivo vectori128.h. (Veja também T. Granlund e PL Montgomery: Divisão por inteiros invariantes usando a multiplicação, continuações do SIGPLAN. “…

Edit: perto do final do arquivo vectori128.h mostra como fazer uma divisão curta com uma variável escalar “Leva mais tempo para calcular os parâmetros usados ​​para divisão rápida do que para fazer a divisão. Portanto, é vantajoso usar o mesmo divisor Por exemplo, para dividir 80 inteiros curtos não assinados por 10:

 short x = 10; uint16_t dividends[80], quotients[80]; // numbers to work with Divisor_us div10(x); // make divisor object for dividing by 10 Vec8us temp; // temporary vector for (int i = 0; i < 80; i += 8) { // loop for 4 elements per iteration temp.load(dividends+i); // load 4 elements temp /= div10; // divide each element by 10 temp.store(quotients+i); // store 4 elements } 

"

Editar: divisão inteira por um vetor de calções

 #include  #include "vectorclass.h" int main() { short numa[] = {10, 20, 30, 40, 50, 60, 70, 80}; short dena[] = {10, 20, 30, 40, 50, 60, 70, 80}; Vec8s num = Vec8s().load(numa); Vec8s den = Vec8s().load(dena); Vec4f num_low = to_float(extend_low(num)); Vec4f num_high = to_float(extend_high(num)); Vec4f den_low = to_float(extend_low(den)); Vec4f den_high = to_float(extend_high(den)); Vec4f qf_low = num_low/den_low; Vec4f qf_high = num_high/den_high; Vec4i q_low = truncate_to_int(qf_low); Vec4i q_high = truncate_to_int(qf_high); Vec8s q = compress(q_low, q_high); for(int i=0; i<8; i++) { printf("%d ", q[i]); } printf("\n"); } 

isto é para recém-chegados graças à solução original: a biblioteca de sub-rotinas da Agner Fog fez a mágica comigo na otimização

aqui está a situação quando você divide no mesmo valor de variável várias vezes (como no loop grande)

 #include  unsigned int a, b, d; unsigned int divisor = any_random_value; div_u32 OptimumDivision(divisor); a/OptimumDivision; b/OptimumDivision; 

isso é para unsigned int – se você precisar de valor negativo use div_i32 vez disso, o que foi mais rápido em meus testes, mesmo que o manual diga o contrário

Eu tenho cerca de 3x desempenho ou mais

A matemática diz que é realmente possível ir mais rápido

O método Agner Fog ( http://www.agner.org/optimize/#vectorclass ) funciona muito bem se a divisão é feita com um único divisor. Além disso, esse método tem ainda mais benefícios se o divisor for conhecido em tempo de compilation ou se não mudar com frequência em tempo de execução.

No entanto, ao executar a divisão SIMD em elementos __m128i modo que nenhuma informação esteja disponível para o divisor e o dividendo no momento da compilation, não temos opção, mas para converter em float e executar o cálculo. Por outro lado, usar _mm_div_ps não nos proporcionará melhorias de velocidade incríveis, já que essa instrução tem latência variável de 11 a 14 ciclos na maioria das micro-arquiteturas e às vezes pode ir até 38 ciclos se considerarmos o Knights Landing. Além disso, essa instrução não é totalmente canalizada e tem taxa de transferência recíproca de 3-6 ciclos, dependendo da microarquitetura.

No entanto, podemos evitar _mm_div_ps e usar _mm_rcp_ss .

Infelizmente __m128 _mm_rcp_ss (__m128 a) é rápido apenas porque fornece aproximação. A saber (retirado do Guia Intel Intrnisics ):

Calcule a recíproca aproximada do elemento de ponto flutuante de precisão única (32 bits) inferior em a, armazene o resultado no elemento inferior de dst e copie os 3 elementos compactados superiores de um para os elementos superiores de dst. O erro relativo máximo para essa aproximação é menor que 1,5 * 2 ^ -12.

Portanto, para se beneficiar de _mm_rcp_ss , precisamos compensar a perda devido à aproximação. Há um grande trabalho feito nessa direção, disponível em Divisão aprimorada por inteiros invariantes de Niels Möller e Torbjörn Granlund :

Devido à falta de instruções de divisão eficientes nos processadores atuais, a divisão é executada como uma multiplicação usando uma aproximação de única palavra pré-computada da recíproca do divisor, seguida por um par de etapas de ajuste.

Para calcular a divisão inteira assinada de 16 bits, precisamos apenas de uma etapa de ajuste e baseamos nossa solução adequadamente.

SSE2

 static inline __m128i _mm_div_epi16(const __m128i &a_epi16, const __m128i &b_epi16) { // // Setup the constants. // const __m128 two = _mm_set1_ps(2.00000051757f); const __m128i lo_mask = _mm_set1_epi32(0xFFFF); // // Convert to two 32-bit integers // const __m128i a_hi_epi32 = _mm_srai_epi32(a_epi16, 16); const __m128i a_lo_epi32_shift = _mm_slli_epi32(a_epi16, 16); const __m128i a_lo_epi32 = _mm_srai_epi32(a_lo_epi32_shift, 16); const __m128i b_hi_epi32 = _mm_srai_epi32(b_epi16, 16); const __m128i b_lo_epi32_shift = _mm_slli_epi32(b_epi16, 16); const __m128i b_lo_epi32 = _mm_srai_epi32(b_lo_epi32_shift, 16); // // Convert to 32-bit floats // const __m128 a_hi = _mm_cvtepi32_ps(a_hi_epi32); const __m128 a_lo = _mm_cvtepi32_ps(a_lo_epi32); const __m128 b_hi = _mm_cvtepi32_ps(b_hi_epi32); const __m128 b_lo = _mm_cvtepi32_ps(b_lo_epi32); // // Calculate the reciprocal // const __m128 b_hi_rcp = _mm_rcp_ps(b_hi); const __m128 b_lo_rcp = _mm_rcp_ps(b_lo); // // Calculate the inverse // #ifdef __FMA__ const __m128 b_hi_inv_1 = _mm_fnmadd_ps(b_hi_rcp, b_hi, two); const __m128 b_lo_inv_1 = _mm_fnmadd_ps(b_lo_rcp, b_lo, two); #else const __m128 b_mul_hi = _mm_mul_ps(b_hi_rcp, b_hi); const __m128 b_mul_lo = _mm_mul_ps(b_lo_rcp, b_lo); const __m128 b_hi_inv_1 = _mm_sub_ps(two, b_mul_hi); const __m128 b_lo_inv_1 = _mm_sub_ps(two, b_mul_lo); #endif // // Compensate for the loss // const __m128 b_hi_rcp_1 = _mm_mul_ps(b_hi_rcp, b_hi_inv_1); const __m128 b_lo_rcp_1 = _mm_mul_ps(b_lo_rcp, b_lo_inv_1); // // Perform the division by multiplication // const __m128 hi = _mm_mul_ps(a_hi, b_hi_rcp_1); const __m128 lo = _mm_mul_ps(a_lo, b_lo_rcp_1); // // Convert back to integers // const __m128i hi_epi32 = _mm_cvttps_epi32(hi); const __m128i lo_epi32 = _mm_cvttps_epi32(lo); // // Zero-out the unnecessary parts // const __m128i hi_epi32_shift = _mm_slli_epi32(hi_epi32, 16); #ifdef __SSE4_1__ // // Blend the bits, and return // return _mm_blend_epi16(lo_epi32, hi_epi32_shift, 0xAA); #else // // Blend the bits, and return // const __m128i lo_epi32_mask = _mm_and_si128(lo_epi32, const_mm_div_epi16_lo_mask); return _mm_or_si128(hi_epi32_shift, lo_epi32_mask); #endif } 

Esta solução pode funcionar apenas com SSE2 e utilizará o FMA se disponível. No entanto, pode ser que usar divisão simples seja tão rápido (ou até mais rápido) quanto usar a aproximação.

Na presença do AVX esta solução pode ser melhorada, pois as partes alta e baixa podem ser processadas ao mesmo tempo usando um registro AVX .

Validação

Como estamos lidando apenas com 16 bits, podemos facilmente validar a correção da solução em poucos segundos usando o teste de força bruta:

  void print_epi16(__m128i a) { int i; int16_t tmp[8]; _mm_storeu_si128( (__m128i*) tmp, a); for (i = 0; i < 8; i += 1) { printf("%8d ", (int) tmp[i]); } printf("\n"); } bool run_mm_div_epi16(const int16_t *a, const int16_t *b) { const size_t n = 8; int16_t result_expected[n]; int16_t result_obtained[n]; // // Derive the expected result // for (size_t i = 0; i < n; i += 1) { result_expected[i] = a[i] / b[i]; } // // Now perform the computation // const __m128i va = _mm_loadu_si128((__m128i *) a); const __m128i vb = _mm_loadu_si128((__m128i *) b); const __m128i vr = _mm_div_epi16(va, vb); _mm_storeu_si128((__m128i *) result_obtained, vr); // // Check for array equality // bool eq = std::equal(result_obtained, result_obtained + n, result_expected); if (!eq) { cout << "Testing of _mm_div_epi16 failed" << endl << endl; cout << "a: "; print_epi16(va); cout << "b: "; print_epi16(vb); cout << endl; cout << "results_obtained: "; print_epi16(vr); cout << "results_expected: "; print_epi16(_mm_loadu_si128((__m128i *) result_expected)); cout << endl; } return eq; } void test_mm_div_epi16() { const int n = 8; bool correct = true; // // Brute-force testing // int16_t a[n]; int16_t b[n]; for (int32_t i = INT16_MIN; correct && i <= INT16_MAX; i += n) { for (int32_t j = 0; j < n; j += 1) { a[j] = (int16_t) (i + j); } for (int32_t j = INT16_MIN; correct && j < 0; j += 1) { const __m128i jv = _mm_set1_epi16((int16_t) j); _mm_storeu_si128((__m128i *) b, jv); correct = correct && run_mm_div_epi16(a, b); } for (int32_t j = 1; correct && j <= INT16_MAX; j += 1) { const __m128i jv = _mm_set1_epi16((int16_t) j); _mm_storeu_si128((__m128i *) b, jv); correct = correct && run_mm_div_epi16(a, b); } } if (correct) { cout << "Done!" << endl; } else { cout << "_mm_div_epi16 can not be validated" << endl; } } 

AVX2

Tendo a solução acima, a implementação do AVX2 é direta:

 static inline __m256i _mm256_div_epi16(const __m256i &a_epi16, const __m256i &b_epi16) { // // Setup the constants. // const __m256 two = _mm256_set1_ps(2.00000051757f); // // Convert to two 32-bit integers // const __m256i a_hi_epi32 = _mm256_srai_epi32(a_epi16, 16); const __m256i a_lo_epi32_shift = _mm256_slli_epi32(a_epi16, 16); const __m256i a_lo_epi32 = _mm256_srai_epi32(a_lo_epi32_shift, 16); const __m256i b_hi_epi32 = _mm256_srai_epi32(b_epi16, 16); const __m256i b_lo_epi32_shift = _mm256_slli_epi32(b_epi16, 16); const __m256i b_lo_epi32 = _mm256_srai_epi32(b_lo_epi32_shift, 16); // // Convert to 32-bit floats // const __m256 a_hi = _mm256_cvtepi32_ps(a_hi_epi32); const __m256 a_lo = _mm256_cvtepi32_ps(a_lo_epi32); const __m256 b_hi = _mm256_cvtepi32_ps(b_hi_epi32); const __m256 b_lo = _mm256_cvtepi32_ps(b_lo_epi32); // // Calculate the reciprocal // const __m256 b_hi_rcp = _mm256_rcp_ps(b_hi); const __m256 b_lo_rcp = _mm256_rcp_ps(b_lo); // // Calculate the inverse // const __m256 b_hi_inv_1 = _mm256_fnmadd_ps(b_hi_rcp, b_hi, two); const __m256 b_lo_inv_1 = _mm256_fnmadd_ps(b_lo_rcp, b_lo, two); // // Compensate for the loss // const __m256 b_hi_rcp_1 = _mm256_mul_ps(b_hi_rcp, b_hi_inv_1); const __m256 b_lo_rcp_1 = _mm256_mul_ps(b_lo_rcp, b_lo_inv_1); // // Perform the division by multiplication // const __m256 hi = _mm256_mul_ps(a_hi, b_hi_rcp_1); const __m256 lo = _mm256_mul_ps(a_lo, b_lo_rcp_1); // // Convert back to integers // const __m256i hi_epi32 = _mm256_cvttps_epi32(hi); const __m256i lo_epi32 = _mm256_cvttps_epi32(lo); // // Blend the low and the high-parts // const __m256i hi_epi32_shift = _mm256_slli_epi32(hi_epi32, 16); return _mm256_blend_epi16(lo_epi32, hi_epi32_shift, 0xAA); } 

Podemos usar o mesmo método descrito acima para realizar a validação do código.

atuação

Podemos avaliar o desempenho usando os flops de medida por ciclo (F / C). Neste cenário, gostamos de ver quantas divisões podemos realizar por ciclo. Para isso, definimos dois vetores b e realizamos a divisão por pontos. Ambos b são preenchidos com dados randoms usando xorshift32 , inicializados com uint32_t state = 3853970173;

Eu uso RDTSC para medir os ciclos, realizando 15 repetições com cache quente e usando a mediana como resultado. Para evitar os efeitos de escala de frequência e compartilhamento de resources nas medições, o Turbo Boost e o Hyper-Threading estão desativados. Para rodar o código eu utilizo o Intel Xeon CPU E3-1285L v3 3.10GHz Haswell com 32GB de RAM e 25,6 GB / s de largura de banda para memory principal, rodando Debian GNU / Linux 8 (jessie), kernel 3.16.43-2+deb8u3 . gcc usado é 4.9.2-10 . Os resultados são fornecidos abaixo:

Implementação Pure SSE2

Nós comparamos a divisão simples com o algoritmo proposto acima:

 =============================================================== = Compiler & System info =============================================================== Current CPU : Intel(R) Xeon(R) CPU E3-1285L v3 @ 3.10GHz CXX Compiler ID : GNU CXX Compiler Path : /usr/bin/c++ CXX Compiler Version : 4.9.2 CXX Compiler Flags : -O3 -std=c++11 -msse2 -mno-fma -------------------------------------------------------------------------------- | Size | Division F/C | Division B/W | Approx. F/C | Approximation B/W | -------------------------------------------------------------------------------- | 128 | 0.5714286 | 26911.45 MB/s | 0.5019608 | 23634.21 MB/s | | 256 | 0.5714286 | 26909.17 MB/s | 0.5039370 | 23745.44 MB/s | | 512 | 0.5707915 | 26928.14 MB/s | 0.5039370 | 23763.79 MB/s | | 1024 | 0.5707915 | 26936.33 MB/s | 0.5039370 | 23776.85 MB/s | | 2048 | 0.5709507 | 26938.51 MB/s | 0.5039370 | 23780.25 MB/s | | 4096 | 0.5708711 | 26940.56 MB/s | 0.5039990 | 23782.65 MB/s | | 8192 | 0.5708711 | 26940.16 MB/s | 0.5039370 | 23781.85 MB/s | | 16384 | 0.5704735 | 26921.76 MB/s | 0.4954040 | 23379.24 MB/s | | 32768 | 0.5704537 | 26921.26 MB/s | 0.4954639 | 23382.13 MB/s | | 65536 | 0.5703147 | 26914.53 MB/s | 0.4943539 | 23330.13 MB/s | | 131072 | 0.5691680 | 26860.21 MB/s | 0.4929539 | 23264.40 MB/s | | 262144 | 0.5690618 | 26855.60 MB/s | 0.4929187 | 23262.22 MB/s | | 524288 | 0.5691378 | 26858.75 MB/s | 0.4929488 | 23263.56 MB/s | | 1048576 | 0.5677474 | 26794.14 MB/s | 0.4918968 | 23214.34 MB/s | | 2097152 | 0.5371243 | 25348.39 MB/s | 0.4700511 | 22183.07 MB/s | | 4194304 | 0.5128146 | 24200.82 MB/s | 0.4529809 | 21377.28 MB/s | | 8388608 | 0.5036971 | 23770.36 MB/s | 0.4438345 | 20945.84 MB/s | | 16777216 | 0.5005390 | 23621.14 MB/s | 0.4409909 | 20811.32 MB/s | | 33554432 | 0.4992792 | 23561.90 MB/s | 0.4399777 | 20763.49 MB/s | -------------------------------------------------------------------------------- 

Podemos observar como a divisão simples será ligeiramente mais rápida que a etapa de aproximação proposta. Neste cenário, podemos concluir que a utilização da aproximação SSE2 será sub-ótima, em uma microarquitetura Haswell.

No entanto, se executarmos os mesmos resultados em uma máquina Sandy Bridge mais antiga, como a Intel Xeon (R) X5680 @ 3.33GHz, já podemos ver o benefício da aproximação:

 =============================================================== = Compiler & System info =============================================================== Current CPU : Intel(R) Xeon(R) CPU X5680 @ 3.33GHz CXX Compiler ID : GNU CXX Compiler Path : /usr/bin/c++ CXX Compiler Version : 4.8.5 CXX Compiler Flags : -O3 -std=c++11 -msse2 -mno-fma -------------------------------------------------------------------------------- | Size | Division F/C | Division B/W | Approx. F/C | Approximation B/W | -------------------------------------------------------------------------------- | 128 | 0.2857143 | 14511.41 MB/s | 0.3720930 | 18899.89 MB/s | | 256 | 0.2853958 | 14512.51 MB/s | 0.3715530 | 18898.91 MB/s | | 512 | 0.2853958 | 14510.53 MB/s | 0.3715530 | 18896.44 MB/s | | 1024 | 0.2853162 | 14511.81 MB/s | 0.3700759 | 18824.00 MB/s | | 2048 | 0.2853162 | 14511.04 MB/s | 0.3708130 | 18860.31 MB/s | | 4096 | 0.2852964 | 14511.16 MB/s | 0.3711826 | 18879.27 MB/s | | 8192 | 0.2852666 | 14510.23 MB/s | 0.3713172 | 18886.39 MB/s | | 16384 | 0.2852616 | 14509.86 MB/s | 0.3712920 | 18885.60 MB/s | | 32768 | 0.2852244 | 14507.93 MB/s | 0.3712709 | 18884.86 MB/s | | 65536 | 0.2851003 | 14501.41 MB/s | 0.3701114 | 18826.14 MB/s | | 131072 | 0.2850711 | 14499.95 MB/s | 0.3685017 | 18743.58 MB/s | | 262144 | 0.2850745 | 14500.47 MB/s | 0.3684799 | 18742.78 MB/s | | 524288 | 0.2848062 | 14486.66 MB/s | 0.3681040 | 18723.63 MB/s | | 1048576 | 0.2846679 | 14479.64 MB/s | 0.3671284 | 18674.02 MB/s | | 2097152 | 0.2840133 | 14446.52 MB/s | 0.3664623 | 18640.01 MB/s | | 4194304 | 0.2745241 | 13963.13 MB/s | 0.3488823 | 17745.24 MB/s | | 8388608 | 0.2741900 | 13946.39 MB/s | 0.3476036 | 17680.37 MB/s | | 16777216 | 0.2740689 | 13940.32 MB/s | 0.3477076 | 17685.97 MB/s | | 33554432 | 0.2746752 | 13970.75 MB/s | 0.3482017 | 17711.36 MB/s | -------------------------------------------------------------------------------- 

Seria ainda melhor ver como isso se comportaria em máquinas ainda mais antigas, diz Nehalem (supondo que ele tenha suporte a RCP ).

Implementação SSE41 + FMA

Comparamos a divisão simples com o algoritmo proposto acima, permitindo FMA e SSE41

 =============================================================== = Compiler & System info =============================================================== Current CPU : Intel(R) Xeon(R) CPU E3-1285L v3 @ 3.10GHz CXX Compiler ID : GNU CXX Compiler Path : /usr/bin/c++ CXX Compiler Version : 4.9.2 CXX Compiler Flags : -O3 -std=c++11 -msse4.1 -mfma -------------------------------------------------------------------------------- | Size | Division F/C | Division B/W | Approx. F/C | Approximation B/W | -------------------------------------------------------------------------------- | 128 | 0.5714286 | 26884.20 MB/s | 0.5423729 | 25506.41 MB/s | | 256 | 0.5701559 | 26879.92 MB/s | 0.5412262 | 25503.95 MB/s | | 512 | 0.5701559 | 26904.68 MB/s | 0.5423729 | 25584.65 MB/s | | 1024 | 0.5704735 | 26911.46 MB/s | 0.5429480 | 25622.57 MB/s | | 2048 | 0.5704735 | 26915.03 MB/s | 0.5433802 | 25640.09 MB/s | | 4096 | 0.5703941 | 26917.72 MB/s | 0.5435965 | 25651.63 MB/s | | 8192 | 0.5703544 | 26915.85 MB/s | 0.5436687 | 25656.76 MB/s | | 16384 | 0.5699972 | 26898.44 MB/s | 0.5262583 | 24834.54 MB/s | | 32768 | 0.5699873 | 26898.93 MB/s | 0.5262076 | 24833.21 MB/s | | 65536 | 0.5698882 | 26894.48 MB/s | 0.5250567 | 24778.35 MB/s | | 131072 | 0.5697024 | 26885.50 MB/s | 0.5224302 | 24654.59 MB/s | | 262144 | 0.5696950 | 26884.72 MB/s | 0.5223095 | 24649.49 MB/s | | 524288 | 0.5696937 | 26885.37 MB/s | 0.5223308 | 24650.21 MB/s | | 1048576 | 0.5690340 | 26854.14 MB/s | 0.5220133 | 24634.71 MB/s | | 2097152 | 0.5455717 | 25746.56 MB/s | 0.5041949 | 23794.65 MB/s | | 4194304 | 0.5125461 | 24188.11 MB/s | 0.4756604 | 22447.05 MB/s | | 8388608 | 0.5043430 | 23800.67 MB/s | 0.4659974 | 21991.51 MB/s | | 16777216 | 0.5017375 | 23677.94 MB/s | 0.4614457 | 21776.58 MB/s | | 33554432 | 0.5005865 | 23623.50 MB/s | 0.4596277 | 21690.63 MB/s | -------------------------------------------------------------------------------- 

FMA + SSE4.1 nos dá algum nível de melhorias, mas isso não é bom o suficiente.

Implementação AVX2 + FMA

Finalmente, podemos ver um benefício real comparando a divisão simples do AVX2 com o método de aproximação:

 =============================================================== = Compiler & System info =============================================================== Current CPU : Intel(R) Xeon(R) CPU E3-1285L v3 @ 3.10GHz CXX Compiler ID : GNU CXX Compiler Path : /usr/bin/c++ CXX Compiler Version : 4.9.2 CXX Compiler Flags : -O3 -std=c++11 -march=haswell -------------------------------------------------------------------------------- | Size | Division F/C | Division B/W | Approx. F/C | Approximation B/W | -------------------------------------------------------------------------------- | 128 | 0.5663717 | 26672.73 MB/s | 0.9481481 | 44627.89 MB/s | | 256 | 0.5651214 | 26653.72 MB/s | 0.9481481 | 44651.56 MB/s | | 512 | 0.5644983 | 26640.36 MB/s | 0.9463956 | 44660.99 MB/s | | 1024 | 0.5657459 | 26689.41 MB/s | 0.9552239 | 45044.21 MB/s | | 2048 | 0.5662151 | 26715.40 MB/s | 0.9624060 | 45405.33 MB/s | | 4096 | 0.5663717 | 26726.27 MB/s | 0.9671783 | 45633.64 MB/s | | 8192 | 0.5664500 | 26732.42 MB/s | 0.9688941 | 45724.83 MB/s | | 16384 | 0.5699377 | 26896.04 MB/s | 0.9092624 | 42909.11 MB/s | | 32768 | 0.5699675 | 26897.85 MB/s | 0.9087077 | 42883.21 MB/s | | 65536 | 0.5699625 | 26898.59 MB/s | 0.9001456 | 42480.91 MB/s | | 131072 | 0.5699253 | 26896.38 MB/s | 0.8926057 | 42124.09 MB/s | | 262144 | 0.5699117 | 26895.58 MB/s | 0.8928610 | 42137.13 MB/s | | 524288 | 0.5698622 | 26892.87 MB/s | 0.8928002 | 42133.63 MB/s | | 1048576 | 0.5685829 | 26833.13 MB/s | 0.8894302 | 41974.25 MB/s | | 2097152 | 0.5558453 | 26231.90 MB/s | 0.8371921 | 39508.55 MB/s | | 4194304 | 0.5224387 | 24654.67 MB/s | 0.7436747 | 35094.81 MB/s | | 8388608 | 0.5143588 | 24273.46 MB/s | 0.7185252 | 33909.08 MB/s | | 16777216 | 0.5107452 | 24103.19 MB/s | 0.7133449 | 33664.28 MB/s | | 33554432 | 0.5101245 | 24074.10 MB/s | 0.7125114 | 33625.03 MB/s | -------------------------------------------------------------------------------- 

Conclusão

Este método pode definitivamente fornecer uma aceleração contra a divisão simples. Quanta aceleração pode realmente ser obtida, isso realmente depende da arquitetura subjacente, bem como de como a divisão interage com o restante da lógica do aplicativo.