如何优化strtok +环礁

时间:2016-06-28 08:50:03

标签: c++ string performance optimization

有没有什么好方法可以在执行时间方面优化此功能?我的最终目标是解析由几个整数组成的长字符串(每行数千个整数,数千行)。这是我最初的解决方案。

int64_t get_next_int(char *newLine) {
    char *token=strtok(newLine, " ");
    if( token == NULL ) {
        exit(0);
    }
    return atoll(token);
}

更多细节:我需要基于“状态”的strtok实现,因此strtok实现的填充应存在于最终字符串中。环礁不需要任何形式的验证。

目标系统:Intel x86_64(Xeon系列)

相关主题

4 个答案:

答案 0 :(得分:2)

我宁愿使用std::istringstream

的内容
int64_t get_next_int(std::istringstream& line) {
    int64_t token;
    if(!(line >> token))
         exit(0);
    return token;
}


 std::istringstream line(newLine);

 int64_t i = get_next_int(line);

strtok()众所周知drawbacks,您根本不想使用它。

答案 1 :(得分:2)

首先关闭:我发现信号处理链中的优化字符串转换例程大部分时间都是徒劳的。系统以字符串形式加载数据的速度(这可能发生在某些大容量存储中,而这些存储是由不关心性能的东西放置的,因为它不会选择字符串格式第一个地方,否则),如果你比较通过PCIe连接的所有SSD集群的读取速度和atoll的速度,你会注意到你失去的时间可以忽略不计转换效率低下。如果您通过转换管道加载该字符串的部分,那么等待存储所花费的时间甚至不会被转换远程填充,因此即使没有任何算法优化,流水线操作/多线程也几乎可以消除所有转换时间。

我将继续并假设您的包含整数的字符串足够大。就像,数以千万计的整数。否则,所有优化都可能还为时过早,因为我们很少抱怨std::iostream performance

现在,诀窍是,一旦转换例程的性能达到内存带宽障碍,就无法进行性能优化。为了尽可能地推动这一障碍,优化CPU缓存的使用至关重要 - 因此,尽可能少地进行线性访问和重排内存至关重要。此外,如果你关心速度,你不想在每次需要转换几位数时调用一个函数 - 调用开销(保存/恢复堆栈,来回跳转)将是重要的。因此,如果您在演出结束后,您将立即转换整个字符串,然后只访问生成的整数数组。

所以你在现代的支持SSE4.2的x86处理器上有类似的东西

外循环,以16的步长跳跃:

  • 将128位输入字符串加载到128位SIMD寄存器
  • 运行类似__mm_cmpestri的内容,一次性查找所有这16个字节中分隔符和\0终结符的索引
  • 找到索引的内循环
    • 使用SSE复制/移位/立即指令隔离子串;用0
    • 填写其他人
    • 预先保存"最后一个字符"来自上一次迭代(如果有的话 - 应该只是每次外循环迭代的第一次内循环迭代的情况)
    • 从每个数字中减去0,再次使用SSE指令通过一条指令进行最多16次减法(_mm_sub_epi8
    • 将8个16位子字转换为8个128位字,每个字包含两个打包的64位整数(每16位一条指令,_mm_cvtepi8_epi64,我认为)
    • 使用__mm128初始化[10^15 10^14]注册,让我们将其称为powers
    • 循环对双64位字:(每一步应该是一条SSE指令)
      • 首先与powers
      • 相乘
      • [100 100]
      • 划分权限
      • powers
      • 相乘
      • 将结果添加到双64位累加器
    • 将累加器中的两个值相加
    • store结果为整数数组

答案 2 :(得分:1)

怎么样?
int n= 0;

// Find the token
for ( ; *newline == ' '; newline++)
  ;
if (*newline == 0)
  // Not found
  exit(0);

// Scan and convert the token
for ( ; unsigned(*newline - '0') < 10; newline++)
  n= 10 * n + *newline - '0';
return n;

答案 3 :(得分:1)

AFA我首先从您的代码中获取它将返回。它似乎在第一次解析时(在空格字符之前),如果它是非数字输入或者以字母开头的方式组合字母和数字,它将返回0。如果在开头合并并且数字,它将仅返回数字。也就是说,您只需要一个字符串进行转换。因此,您不需要进行标记,只需检查string是否为空。您也可以更改返回类型。因为,如果你需要一个_exactly_64位的类型,使用(u)int64_t,如果你需要_at至64位,(unsigned) long long完全没问题,(u)int_least64_t 。我认为你的代码很少gobbledygook。在没有简化的情况下显示您想要的内容。

/*
 * ascii-to-longlong conversion
 *
 * no error checking; assumes decimal digits
 *
 * efficient conversion: 
 *   start with value = 0
 *   then, starting at first character, repeat the following
 *   until the end of the string:
 *
 *     new value = (10 * (old value)) + decimal value of next character
 *
 */
long long my_atoll(char *instr)
{
  if(str[0] == '\0')
    return -1;

  long long retval;
  int i;

  retval = 0;
  for (; *instr; instr++) {
    retval = 10*retval + (*instr - '0');
  }
  return retval;
}