Я реализовал функцию strlen() по-разному, включая SSE2 assembly, SSE4.2 assembly и SSE2 intrinsic, я также провел несколько экспериментов над ними с strlen() in <string.h> и strlen() in glibc. Однако их производительность в миллисекундах (время) неожиданна.
Моя экспериментальная среда:
CentOS 7.0 + gcc 4.8.5 + Intel Xeon
Ниже приведены мои реализации:
-  strlenс помощью сборки SSE2long strlen_sse2_asm(const char* src){ long result = 0; asm( "movl %1, %%edi\n\t" "movl $-0x10, %%eax\n\t" "pxor %%xmm0, %%xmm0\n\t" "lloop:\n\t" "addl $0x10, %%eax\n\t" "movdqu (%%edi,%%eax), %%xmm1\n\t" "pcmpeqb %%xmm0, %%xmm1\n\t" "pmovmskb %%xmm1, %%ecx\n\t" "test %%ecx, %%ecx\n\t" "jz lloop\n\t" "bsf %%ecx, %%ecx\n\t" "addl %%ecx, %%eax\n\t" "movl %%eax, %0" :"=r"(result) :"r"(src) :"%eax" ); return result; }
2. strlen с использованием сборки SSE4.2
long strlen_sse4_2_asm(const char* src){
long result = 0;
asm(
    "movl %1, %%edi\n\t"
    "movl $-0x10, %%eax\n\t"
    "pxor %%xmm0, %%xmm0\n\t"
    "lloop2:\n\t"
        "addl $0x10, %%eax\n\t"
        "pcmpistri $0x08,(%%edi, %%eax), %%xmm0\n\t"
        "jnz lloop2\n\t"
        "add %%ecx, %%eax\n\t"
        "movl %%eax, %0"
    :"=r"(result)
    :"r"(src)
    :"%eax"
    );
return result;
}
3. strlen с использованием встроенного SSE2
long strlen_sse2_intrin_align(const char* src){
if (src == NULL || *src == '\0'){
    return 0;
}
const __m128i zero = _mm_setzero_si128();
const __m128i* ptr = (const __m128i*)src;
if(((size_t)ptr&0xF)!=0){
    __m128i xmm = _mm_loadu_si128(ptr);
    unsigned int mask = _mm_movemask_epi8(_mm_cmpeq_epi8(xmm,zero));
    if(mask!=0){
        return (const char*)ptr-src+(size_t)ffs(mask);
    }
    ptr = (__m128i*)(0x10+(size_t)ptr & ~0xF);
}
for (;;ptr++){
    __m128i xmm = _mm_load_si128(ptr);
    unsigned int mask = _mm_movemask_epi8(_mm_cmpeq_epi8(xmm,zero));
    if (mask!=0)
        return (const char*)ptr-src+(size_t)ffs(mask);
}
}
-  Я также просмотрел версию, реализованную в ядре linux, следующая ее реализация size_t strlen_inline_asm(const char* str){ int d0; size_t res; asm volatile("repne\n\t" "scasb" :"=c" (res), "=&D" (d0) : "1" (str), "a" (0), "" (0xffffffffu) : "memory"); return ~res-1; }
По моему опыту, я также добавил стандартную библиотеку и сравнил их производительность.
Следующим является код функции main:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <xmmintrin.h>
#include <x86intrin.h>
#include <emmintrin.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
int main()
{
    struct timeval tpstart,tpend;
    int i=0;
    for(;i<1023;i++){
            test_str[i] = 'a';
    }
    test_str[i]='\0';
    gettimeofday(&tpstart,NULL);
    for(i=0;i<10000000;i++)
            strlen(test_str);
    gettimeofday(&tpend,NULL);
    printf("strlen from stirng.h--->%lf\n",(tpend.tv_sec-tpstart.tv_sec)*1000+(tpend.tv_usec-tpstart.tv_usec)/1000.0);
    gettimeofday(&tpstart,NULL);
    for(i=0;i<10000000;i++)
            strlen_inline_asm(test_str);
    gettimeofday(&tpend,NULL);
    printf("strlen_inline_asm--->%lf\n",(tpend.tv_sec-tpstart.tv_sec)*1000+(tpend.tv_usec-tpstart.tv_usec)/1000.0);
    gettimeofday(&tpstart,NULL);
    for(i=0;i<10000000;i++)
            strlen_sse2_asm(test_str);
    gettimeofday(&tpend,NULL);
    printf("strlen_sse2_asm--->%lf\n",(tpend.tv_sec-tpstart.tv_sec)*1000+(tpend.tv_usec-tpstart.tv_usec)/1000.0);
    gettimeofday(&tpstart,NULL);
    for(i=0;i<10000000;i++)
            strlen_sse4_2_asm(test_str);
    gettimeofday(&tpend,NULL);
    printf("strlen_sse4_2_asm--->%lf\n",(tpend.tv_sec-tpstart.tv_sec)*1000+(tpend.tv_usec-tpstart.tv_usec)/1000.0);
    gettimeofday(&tpstart,NULL);
    for(i=0;i<10000000;i++)
            strlen_sse2_intrin_align(test_str);
    gettimeofday(&tpend,NULL);
    printf("strlen_sse2_intrin_align--->%lf\n",(tpend.tv_sec-tpstart.tv_sec)*1000+(tpend.tv_usec-tpstart.tv_usec)/1000.0);
    return 0;
}
Результат: (ms)
strlen from stirng.h--->23.518000
strlen_inline_asm--->222.311000
strlen_sse2_asm--->782.907000
strlen_sse4_2_asm--->955.960000
strlen_sse2_intrin_align--->3499.586000
У меня есть некоторые вопросы по этому поводу:
-  Почему strlenизstring.hработает так быстро? Я думаю, что его код должен быть идентифицирован какstrlen_inline_asm, потому что я скопировал код из/linux-4.2.2/arch/x86/lib/string_32.c[http://lxr.oss.org.cn/source/arch/x86/lib/string_32.c#L164]
-  Почему SSE2 intrinsicиSSE2 assemblyнастолько отличаются по производительности?
-  Может ли кто-нибудь помочь мне разобрать код, чтобы я мог увидеть, что функция strlenстатической библиотеки была преобразована компилятором? Я использовалgcc -s, но не нашел разборкиstrlen from the <string.h>
- Я думаю, что мой код может быть не очень хорошо, я был бы признателен, если бы вы могли помочь мне улучшить свой код, особенно сборки.
Спасибо.
