线程缓存的对象引用

Thread-cached object referencing

本文关键字:对象引用 缓存 线程      更新时间:2023-10-16

我需要创建一种共享对象(无论出于何种原因)。它并不局限于单线程的使用。通常在这种情况下,联锁操作是可行的(例如Win32上的InterlockedIncrementInterlockedDecrement)。

尽管对象引用计数在任何情况下都应该正确工作,但我想针对单线程使用对其进行优化。联锁操作比标准算术操作要重得多。从我的测量中,一个联锁操作(发出全内存屏障)在我的"典型"CPU上大约需要40个CPU周期,而标准算术操作低于任何测量精度(多亏了CPU缓存)。

在内存分配方面也有类似的技术。有一些堆实现,比如"TCMalloc",它由集中的内存分区机制组成,由适当的同步对象保护,再加上每个线程的缓存。在最常见的情况下,在每个线程缓存上分配/释放内存,这根本不涉及任何联锁操作,而且CPU缓存的利用率很高。

因此,我考虑为支持引用的对象做类似的事情的可能性。有什么办法做到这一点吗?我们也欢迎你的原创想法。

在我的场景中,如果可以提高性能,可以将实际的对象销毁延迟一段时间。

我就不麻烦了。我刚刚运行了这个基准测试:

#include<stdio.h>
#define SIZE 1000000
static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}

void print_avg(const char *str, const int *diff, int size)
{
    int i;
    long sum = 0;
    int max = -1, min = 10000;
    for(i = 0; i < size; i++)
    {
    int t = diff[i];
    sum += t;
    if (t > max) max = t;
    if (t < min) min = t;
    }
    printf("%s average =%f clocks, max =%d, min =%dn", str, (double)sum / size, max, min);
}

int main()
{
    unsigned long long a, b;
    int diff[SIZE];
    int value = 0;
    int i;

    for(i = 0; i < SIZE; i++)
    {
    a = rdtsc();
    __sync_fetch_and_add(&value, 2);
    b = rdtsc();
    diff[i] = (int)(b - a);
    }
    print_avg("Locked", diff, SIZE);
    for(i = 0; i < SIZE; i++)
    {
    a = rdtsc();
        value += 2;
    b = rdtsc();
    diff[i] = (int)(b - a);
    }
    print_avg("Not locked", diff, SIZE);
    return 0;
}

使用gcc -O2编译,它给出以下结果:

Locked average =105.672402 clocks, max =38756, min =86  
Not locked average =80.540389 clocks, max =23433, min =73

我运行了几次,每次的结果都非常相似。请忽略max的大数字-这是处理器中断或其他事情的时候-它来自我为不同目的编写的一些代码,我只是在这个测试中循环使用它。这个微小的差异应该适用于所有现代处理器(Intel iCore和AMD Athlon64等几代处理器)

除非你的编译器没有内联InterlockedIncrement,否则在你的代码中添加if语句很可能会花费至少5个周期,所以你最多节省了10个周期。希望您正在做的不是增加和减少引用计数器。

编辑:添加内存屏障也不会产生很大的差异-大约10个周期。

不可否认,如果我在第二个循环中添加10个加法,那么每个循环大约有5个时钟周期(因此平均每次添加半个时钟),而锁定的加法每次添加大约需要20个时钟。在我看来,仍然不值得添加if语句。但是如果你想添加"if (nr_threads == 1) a = a + 1;else a = __sync_fetch_and_add(a, 1);",[或任何你需要做的事]我不会阻止你。但要确保对整个应用程序进行基准测试,并确保改进幅度超过1%——我对此表示怀疑。请务必回来告诉我们有什么不同。我在Linux内核的"deallocate页表项"中添加的if语句使它慢了2-5%,所以不值得这样做。但如果您在代码中发现了这一点,那就值得了,随您的便。我是根据我的经验说的,我有数据可以证明这一点,但如果你想自己尝试一下,当然可以。