记录一次性能问题的解决

最近在用schbench测试机器调度性能。测试是绑定在一个socket上进行的。当worker线程数等于socket包含的cpu数时耗时会发生一次跳变。主要是futex wake的时候执行时间变长。开始怀疑是kernel的执行逻辑发生了变化,但查看ftrace后发现没有变化。第二个怀疑点是kernel执行的某个子函数耗时变大,但是使用ebpf工具测量每个子函数的耗时时发现耗时增量与函数执行时间占比成正比,也就是函数是均匀增加的,感觉是硬件问题引发的程序整体变慢。我想既然是程序整体变慢那就跟测试程序无关,我可以在futex函数内加一些自己的测试代码,看看测试代码会不会跟原来的代码一样变慢。结果正如预想的,插入的独立代码也会变慢。这就证明了整个线程被某种神秘力量拉扯导致整体发生了性能下降。那这股神秘力量是什么呢?

通过观察message线程worker线程所在cpu所属的core发现,worker线程已经占据了socket内所有的物理core,message正在与某个worker线程共享一个物理core。所以怀疑点变为共享core导致的性能下降,这也是非常合理的性能下降的情形。

通过缩小测试范围发现,一旦message线程和worker线程共享core就会发生性能下降的问题。可以确定原因了。(为啥没早点发现,这个数字还是有点明显的)

再通过一些自己创建的测试case去模拟两个线程共享一个物理core,测试影响,测试case如下:

复制代码
#include <pthread.h>
#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>

void* worker(void*);
void* worker1(void*);

int over = 0;

int main() {
        pthread_t thread0;
        pthread_t thread1;
        void * res0;
        void * res1;

        pthread_create(&thread0, NULL, worker, NULL);
        pthread_create(&thread1, NULL, worker1, NULL);
        pthread_join(thread0, &res0);
        pthread_join(thread1, &res1);

        return 0;

}

void tvsub(struct timeval * tdiff, struct timeval * t1, struct timeval * t0)
{
        tdiff->tv_sec = t1->tv_sec - t0->tv_sec;
        tdiff->tv_usec = t1->tv_usec - t0->tv_usec;
        if (tdiff->tv_usec < 0 && tdiff->tv_sec > 0) {
                tdiff->tv_sec--;
                tdiff->tv_usec += 1000000;
                if (tdiff->tv_usec < 0) {
                        fprintf(stderr, "lat_fs: tvsub shows test time ran backwards!\n");
                        exit(1);
                }
        }

        /* time shouldn't go backwards!!! */
        if (tdiff->tv_usec < 0 || t1->tv_sec < t0->tv_sec) {
                tdiff->tv_sec = 0;
                tdiff->tv_usec = 0;
        }
}

unsigned long long tvdelta(struct timeval *start, struct timeval *stop)
{
        struct timeval td;
        unsigned long long usecs;

        tvsub(&td, stop, start);
        usecs = td.tv_sec;
        usecs *= 1000000;
        usecs += td.tv_usec;
        return (usecs);
}

#define nop __asm__ __volatile__("rep;nop": : :"memory")
static void usec_spin(unsigned long spin_time)
{
        struct timeval now;
        struct timeval start;
        unsigned long long delta;

        if (spin_time == 0)
                return;

        gettimeofday(&start, NULL);
        while (1) {
                gettimeofday(&now, NULL);
                delta = tvdelta(&start, &now);
                if (delta > spin_time)
                        return;
                nop;
        }
}

void* worker(void* __unused) {
        long input = 64;
        long count = 0;
        char addr[64];

        int loop = 100000000, i = 0, start = 2, end = 2;
        while (i++ < loop) {
                for(int k = 0; k < input; k++) {
                        addr[k] = 1;
                        count++;
                }
        }

        over = 1;
}

void* worker1(void* __unused) {
        while(!over){
                usec_spin(30000);
        }
}
复制代码

一个线程循环访存+自增,表示工作线程,另一个线程循环getime,表示干扰项。当访存循环结束,整个进程结束。

这个例子可以测量到超线程之间的影响,当然不是什么很好的例子,只是可以模拟schbench中遇到的问题。

结论是:运行在同一个物理core中不同超线程的程序之间会有相互的干扰,在core足够多的情况下应该尽量避免这种调度。所以,调度器希望负载均衡不是没有道理,因亲和性而希望imbalance也是有一些平衡点在内的。这是个矛盾,要因测试case而变。

 感悟:

性能问题不像解bug,bug通过缩小测试范围总能得到一个答案,但是性能问题要考虑的维度太多,根本没有一个显而易见的范围,当你将自己限制在某个预设答案范围内时很可能就跟真正的原因错过了。将自己想象成一台计算机,根据了解到的事实在脑海中模拟运行的过程,靠感觉去寻找答案也许是个不错的办法。

posted on   半山随笔  阅读(28)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示