【发布时间】:2018-05-20 10:43:33
【问题描述】:
我正在为二进制文件编写解析器。数据存储在连续的 32 位记录中。文件只需读取一次,然后将其输入到分析算法中。
现在我正在读取 1024 条记录的文件块,以避免尽可能多地因调用 fread 而不是必要的频率而产生的开销。在下面的示例中,我使用 oflcorrection、timetag 和 channel 作为算法的输出,并使用 bool 返回值来检查算法是否应该停止。另请注意,并非所有记录都包含光子,只有具有正值的记录。
使用这种方法,如果我使用将文件分解成碎片的算法的线程版本,我可以处理高达 0.5GBps 或 1.5GBps 的速度。我知道我的 SSD 读取速度至少快了 40%。我正在考虑使用 SIMD 来并行解析多条记录,但我不知道如何使用条件返回子句来做到这一点。
您知道任何其他方法可以让我将分块阅读和 SIMD 结合起来吗?一般有更好的方法吗?
谢谢
附:这些记录对应于通过分束器后到达探测器的光子或指示溢出情况的特殊记录。需要后者,因为时间标签以皮秒分辨率存储在 uint64_t 中。
static inline bool next_photon(FILE* filehandle, uint64_t * RecNum,
uint64_t StopRecord, record_buf_t *buffer,
uint64_t *oflcorrection, uint64_t *timetag, int *channel)
{
pop_record:
while (__builtin_unpredictable(buffer->head < RECORD_CHUNK)) { // still have records on buffer
ParseHHT2_HH2(buffer->records[buffer->head], channel, timetag, oflcorrection);
buffer->head++;
(*RecNum)++;
if (*RecNum >= StopRecord) { // run out of records
return false;
}
if (*channel >= 0) { // found a photon
return true;
}
}
// run out of buffer
buffer->head = 0;
fread(buffer->records, RECORD_CHUNK, sizeof(uint32_t), filehandle);
goto pop_record;
}
请在下面找到解析功能。请记住,我对文件格式无能为力。再次感谢 Guillem。
static inline void ParseHHT2_HH2(uint32_t record, int *channel,
uint64_t *timetag, uint64_t *oflcorrection)
{
const uint64_t T2WRAPAROUND_V2 = 33554432;
union{
uint32_t allbits;
struct{ unsigned timetag :25;
unsigned channel :6;
unsigned special :1;
} bits;
} T2Rec;
T2Rec.allbits = record;
if(T2Rec.bits.special) {
if(T2Rec.bits.channel==0x3F) { //an overflow record
if(T2Rec.bits.timetag!=0) {
*oflcorrection += T2WRAPAROUND_V2 * T2Rec.bits.timetag;
}
else { // if it is zero it is an old style single overflow
*oflcorrection += T2WRAPAROUND_V2; //should never happen with new Firmware!
}
*channel = -1;
} else if(T2Rec.bits.channel == 0) { //sync
*channel = 0;
} else if(T2Rec.bits.channel<=15) { //markers
*channel = -2;
}
} else {//regular input channel
*channel = T2Rec.bits.channel + 1;
}
*timetag = *oflcorrection + T2Rec.bits.timetag;
}
我想出了一个几乎无分支的解析函数,但它并没有产生任何加速。
if(T2Rec.bits.channel==0x3F) { //an overflow record
*oflcorrection += T2WRAPAROUND_V2 * T2Rec.bits.timetag;
}
*channel = (!T2Rec.bits.special) * (T2Rec.bits.channel + 1) - T2Rec.bits.special * T2Rec.bits.channel;
*timetag = *oflcorrection + T2Rec.bits.timetag;
}
【问题讨论】:
-
label和goto语句是不可取的;目前尚不清楚是否需要它们。你完全可以在 label/goto 循环的主体周围使用
for (;;)或while (1)。你不应该忽略来自fread()的返回值;它告诉您读取了多少数据(如果有)。如果忽略该返回值,则无法编写可靠的代码。 -
ParseHHT2_HH2(buffer->records[buffer->head], channel, timetag, oflcorrection);是做什么的?顺便说一句:传递和取消引用指针看起来很昂贵。 -
你假设,@GuillemB,你的文件总是格式正确并且没有发生 I/O 错误。这些都不是一个安全的假设。检查函数调用的返回值。
-
@JonathanLeffler 和 JohnBollinger。你当然是对的,我痴迷于试图让它快速运行,我认为另一个如果会杀了我。当然不是,因为它很少被调用。在那个话题上,关于通道条件的 if 子句的成本是巨大的。通过消除和(当然杀死之后的算法),我在一个只读取文件中光子总数的微不足道的函数上将解析速度提高了 2 倍。
-
另外:主循环内的条件数量(加上活动表达式的数量)将有效地破坏分支预测。在任何情况下:配置文件并检查生成的汇编源代码。
标签: c performance parsing