我有一段代码用于在将数据从生产者(线程1)传递给使用者(线程2)时测试各种容器(例如deque和循环缓冲区)。数据由具有一对时间戳的结构表示。在推入生产者之前获取第一个时间戳,在消费者弹出数据时获取第二个时间戳。 容器用pthread螺旋锁保护。
机器运行带有2.6.18内核的redhat 5.5(旧!),它是一个禁用超线程的4核系统。所有测试都使用gcc 4.7和-std = c ++ 11标志。
生产者获取锁定,为数据加时间戳并将其推入队列,解锁并在繁忙的循环中休眠2微秒(我发现在该系统上只有2微秒的唯一可靠方式)。
消费者锁定,弹出数据,为其加上时间戳并生成一些统计信息(运行平均延迟和标准差)。统计数据每5秒打印一次(M是平均值,M2是std dev)并重置。我使用gettimeofday()来获取时间戳,这意味着平均延迟数可以被认为是超过1微秒的延迟百分比。
大多数情况下输出如下:
CNT=2500000 M=0.00935 M2=0.910238
CNT=2500000 M=0.0204112 M2=1.57601
CNT=2500000 M=0.0045016 M2=0.372065
但有时候(可能是20次中的1次试验)是这样的:
CNT=2500000 M=0.523413 M2=4.83898
CNT=2500000 M=0.558525 M2=4.98872
CNT=2500000 M=0.581157 M2=5.05889
(注意平均数比第一种情况要糟糕得多,并且随着程序的运行它永远不会恢复。)
我很想知道为什么会这样。感谢。
#include <iostream>
#include <string.h>
#include <stdexcept>
#include <sys/time.h>
#include <deque>
#include <thread>
#include <cstdint>
#include <cmath>
#include <unistd.h>
#include <xmmintrin.h> // _mm_pause()
int64_t timestamp() {
struct timeval tv;
gettimeofday(&tv, 0);
return 1000000L * tv.tv_sec + tv.tv_usec;
}
//running mean and a second moment
struct StatsM2 {
StatsM2() {}
double m = 0;
double m2 = 0;
long count = 0;
inline void update(long x, long c) {
count = c;
double delta = x - m;
m += delta / count;
m2 += delta * (x - m);
}
inline void reset() {
m = m2 = 0;
count = 0;
}
inline double getM2() { // running second moment
return (count > 1) ? m2 / (count - 1) : 0.;
}
inline double getDeviation() {
return std::sqrt(getM2() );
}
inline double getM() { // running mean
return m;
}
};
// pause for usec microseconds using busy loop
int64_t busyloop_microsec_sleep(unsigned long usec) {
int64_t t, tend;
tend = t = timestamp();
tend += usec;
while (t < tend) {
t = timestamp();
}
return t;
}
struct Data {
Data() : time_produced(timestamp() ) {}
int64_t time_produced;
int64_t time_consumed;
};
int64_t sleep_interval = 2;
StatsM2 statsm2;
std::deque<Data> queue;
bool producer_running = true;
bool consumer_running = true;
pthread_spinlock_t spin;
void producer() {
producer_running = true;
while(producer_running) {
pthread_spin_lock(&spin);
queue.push_back(Data() );
pthread_spin_unlock(&spin);
busyloop_microsec_sleep(sleep_interval);
}
}
void consumer() {
int64_t count = 0;
int64_t print_at = 1000000/sleep_interval * 5;
Data data;
consumer_running = true;
while (consumer_running) {
pthread_spin_lock(&spin);
if (queue.empty() ) {
pthread_spin_unlock(&spin);
// _mm_pause();
continue;
}
data = queue.front();
queue.pop_front();
pthread_spin_unlock(&spin);
++count;
data.time_consumed = timestamp();
statsm2.update(data.time_consumed - data.time_produced, count);
if (count >= print_at) {
std::cerr << "CNT=" << count << " M=" << statsm2.getM() << " M2=" << statsm2.getDeviation() << "\n";
statsm2.reset();
count = 0;
}
}
}
int main(void) {
if (pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE) < 0)
exit(2);
std::thread consumer_thread(consumer);
std::thread producer_thread(producer);
sleep(40);
consumer_running = false;
producer_running = false;
consumer_thread.join();
producer_thread.join();
return 0;
}
答案 0 :(得分:1)
编辑:
我相信下面的5是唯一可以解释1/2秒延迟的东西。当在同一个核心上时,每个核心都会运行很长时间,然后才会切换到另一个核心
列表中的其余部分太小而不会导致1/2秒的延迟
您可以使用pthread_setaffinity_np将线程固定到特定的核心。您可以尝试不同的组合,看看性能如何变化。
编辑#2:
你应该注意的更多事情:(谁说测试很简单......)
1.确保生产者开始生产时消费者已经在运行。在你的情况下不太重要,因为生产者并没有真正生产紧密的循环
2. 这是非常重要的 :你每次都按计数除以,这对你的统计数据来说不是正确的。这意味着每个统计窗口中的第一次测量比最后一次重量更多。要衡量中位数,您必须收集所有值。在不收集所有数字的情况下测量平均值和最小值/最大值,可以为您提供足够好的延迟图像。
真的,这并不奇怪
1.时间是在Data()中进行的,但是容器花费时间调用malloc。
你在运行64位还是32位?在32位gettimeofday是一个系统调用,而在64位,它是一个没有进入内核的VDSO ...你可能想要自己gettimeofday本身并记录方差。或者使用rdtsc注册自己
最好的方法是使用循环而不是微观,因为微观对于这种情况实际上太大了......只有微处理器才能在处理如此小规模的事物时非常倾斜
3.您是否保证不会在生产者和消费者之间获得优先权?我猜不是。但这不应该在一个专门用于测试的盒子上频繁发生......
4.单个插座上是4芯还是2个?如果它是一个2插槽盒,你想让2个线程在同一个插槽上,或者你支付(至少)两倍的数据传输。
5.确保螺纹没有在同一个核心上运行
6.如果您传输的数据和其他数据(容器节点)正在与其他Data +节点共享缓存行(可能类型),则消费者在写入消耗的时间戳时会延迟生产者。这称为虚假共享。您可以通过填充/对齐到64字节并使用侵入式容器来消除此问题。
答案 1 :(得分:0)
gettimeofday不是分析计算开销的好方法。它是挂钟,您的计算机是多处理的。即使您认为自己没有运行任何其他东西,操作系统调度程序也总是有一些其他活动来保持系统运行。要分析您的流程开销,您必须至少提高要分析的流程的优先级。还可以使用高分辨率计时器或cpu tick来进行计时测量。