2013年10月31日星期四

未使用mfence引起的bug

前几天为了提高内存分配模块的效率,而使用了自行实现的spin_lock(即采用GCC的内置宏__sync_lock_test_and_set、MSVC的内置宏_InterlockedExchange)

理论上,释放锁时,需要考虑执行乱序的问题,需要有Memory Barrier(即执行mfence)避免临界区内的改动在临界区外生效。不过,我在本机上测试,发现无论如何不会出现乱序的情况,这让我以为处理器有机制避免这种乱序问题。考虑到引入mfence会带来明显的性能下降,我就去掉了这个语句。

结果,QA在服务器上测试时,就发现了异常问题,而且看上去和内存管理有关。在排除了其他bug以后(事实上,这个版本有很多其他各色问题),仍然没有彻底解决。我考虑可能和未使用mfence有关,便重新加上,果然恢复正常了。

测试表明,若无mfence,桌面的处理器和AMD的服务器处理器似乎都不会有乱序执行导致错误,而XEON则很容易出现问题。

一段用于测试乱序执行的代码:

makefile

a.out : main.cpp
    g++ -O2 -g -lpthread -o $@ lt;


main.cpp

#include
#include
#include
#include
#include

volatile unsigned int a1 = 0;
volatile unsigned int b1 = 0;

int lock = 0;

int failed_count = 0;

#define spin_lock(p) while (__sync_lock_test_and_set(p, 1)) __asm("pause")

#if 0
// No mfence
#define spin_unlock(p) *p = 0
#else
// Use mfence
#define spin_unlock(p) __sync_synchronize(); *p = 0
#endif

static void* f(void* arg)
{
    unsigned int c1, d1;

    for(;;)
    {
        spin_lock(&lock);
        c1 = a1;
        d1 = b1;

        a1++;
        b1++;
        spin_unlock(&lock);
        if (c1 != d1)
        {
            failed_count++;
            spin_lock(&lock);
            a1 = b1;
            spin_unlock(&lock);
        }
    }
}

static void* g(void* arg)
{
    struct timeval now, last;
    int us;

    gettimeofday(&last, NULL);
    for(;;)
    {
        spin_lock(&lock);

        b1++;
        a1++;
        if (b1 >= 0x1000000)
            break;

        spin_unlock(&lock);
    }

    gettimeofday(&now, NULL);
    us = (now.tv_sec - last.tv_sec) * 1000000;
    us += (now.tv_usec - last.tv_usec);
    printf("us = %d.\n", us);
    printf("failed_count = %d.\n", failed_count);
    exit(0);
}

int main(int argc, const char* argv[])
{
    pthread_t pid1,pid2;

    if (pthread_create(&pid1, 0, f, 0))
    {
        printf("Create thread1 error\n");
        exit(-1);
    }

    if (pthread_create(&pid2, 0, g, 0))
    {
        printf("Create thread2 error\n");
        exit(-1);
    }

    while (1)
        sleep(1);

    return 0;
}


通过调整#if 0/1的开关,可以选择使用/不使用mfence。

注意:出现failed count说明受到乱序执行的影响;但是若没有出现failed count并不意味不存在问题,可能只是没有引发问题;另外,编译时必须开启优化,否则生成臃肿的代码很可能会掩盖问题。

没有评论:

发表评论