【问题标题】:Fastest way to capitalize words大写单词的最快方法
【发布时间】:2012-05-28 03:57:26
【问题描述】:

使用 C++ 将单词 (std::string) 大写的最快方法是什么?

在使用带有 -O3 标志的 g++ 4.6.3 的 Debian Linux 上,这个使用 boost::to_lower 的函数将在 AMD Phenom(tm) II X6 1090T 处理器 (3200兆赫)。

void Capitalize( std::string& word )
{
    boost::to_lower( word );
    word[0] = toupper( word[0] );
}

这个使用std::transform 的函数在大约10 秒内完成同样的事情。我在测试之间清除了虚拟机,所以我不认为这种差异是侥幸:

sync && echo 3 > /proc/sys/vm/drop_caches

void Capitalize( std::string& word )
{
    std::transform(word.begin(), word.end(), word.begin(), ::tolower);
    word[0] = toupper( word[0] );
}

有更快的方法吗?我不想为了速度而失去可移植性,但是如果有更快的方法可以在 std C++ 或带有 boost 的 std C++ 中工作,我想尝试一下。

谢谢。

【问题讨论】:

  • 资本化真的是瓶颈吗?
  • 要优化,您必须分析代码以确定瓶颈。一旦你知道瓶颈是什么,你就可以设计一个更快的解决方案。另外,您是否比较了两种解决方案的结果,以便知道它们是否真的做同样的事情?或许 boost 适用于 UTF-8 或提供更好的语言环境和 Unicode 支持(反之亦然)。
  • 作为 std::transform 的一个小优化,您可以从 word.begin() + 1 开始,这样您就不会在您要去的第一个字符上调用 tolower发送至toupper
  • 数据从何而来?如果您是从磁盘读取,那么 IO 很可能会成为您的瓶颈。
  • @OliCharlesworth - 是的,这些词来自磁盘上的文件。我意识到 IO 在某些时候可能会是缓慢的部分。我想我还没有达到目标。

标签: c++ boost stdstring


【解决方案1】:

在处理不保证输入为大写且boost::to_upper 是代码中的瓶颈的DNA 序列时遇到了这个确切的问题。改成这样:

template<typename T_it>
void SequenceToUpperCase( T_it begin, T_it end )
{
    // Convert to upper: clear the '32' bit, 0x20 in hex. And with the
    // inverted bit string (~).
    for ( auto it = begin; it != end; ++it )
        *it &= ~0x20;
}

导致速度大幅提升。我确信可以通过例如进一步优化。一次翻转 8 个字节,但使用上面的代码,大写对我们来说几乎是瞬时的。对于小写:做:

        *it |= 0x20;

【讨论】:

    【解决方案2】:

    加快速度的几种方法:
    1.不要用to_lower,很慢。也不要使用ifs,创建一个包含 256 个条目的表,从字符映射到其小写版本,另一个表用于大写。
    2.不要使用transform,取一个指向第一个字符的指针,循环直到空终止符。
    3. 如果内存不是问题,请使用映射 2 个字符序列的表。在这种情况下,您将需要另一个处理终止的表。
    4. 如果能在汇编中做到这一点,会快很多。

    【讨论】:

    • 别以为世界上只有拉丁字母!
    • 你有任何证据支持这些吗?例如,std::transform 通常会完全内联。
    • to_lower 是非标准的。 OTOH,tolower 通常使用查找表实现(鼓励您传递 unsigned int 而不是签名的 int 的原因之一)。
    【解决方案3】:

    如果大写是一个真的瓶颈,那么使用手写循环和内联 toupper/tolower 函数编写自己的大写实现。 必要时使用 ASM。

    【讨论】:

      【解决方案4】:

      我有一个实现,我发现它比 std::transform 更快,在 g++ -03 Fedora 18 中编译。

      以秒为单位的表演时间: 转换耗时:11 秒 我的实施耗时:2 s 测试数据大小 = 26*15*9999999 个字符
      inline void tolowerPtr(char *p) ;
      
      inline void tolowerStr(std::string& s)
      {char* c=const_cast<char*>(s.c_str());
      size_t l = s.size();
        for(char* c2=c;c2<c+l;c2++)tolowerPtr(c2); 
      };
      
      inline void tolowerPtr(char *p) 
      {
      switch(*p)
      {
        case 'A':*p='a'; return;
        case 'B':*p='b'; return;
        case 'C':*p='c'; return;
        case 'D':*p='d'; return;
        case 'E':*p='e'; return;
        case 'F':*p='f'; return;
        case 'G':*p='g'; return;
        case 'H':*p='h'; return;
        case 'I':*p='i'; return;
        case 'J':*p='j'; return;
        case 'K':*p='k'; return;
        case 'L':*p='l'; return;
        case 'M':*p='m'; return;
        case 'N':*p='n'; return;
        case 'O':*p='o'; return;
        case 'P':*p='p'; return;
        case 'Q':*p='q'; return;
        case 'R':*p='r'; return;
        case 'S':*p='s'; return;
        case 'T':*p='t'; return;
        case 'U':*p='u'; return;
        case 'V':*p='v'; return;
        case 'W':*p='w'; return;
        case 'X':*p='x'; return;
        case 'Y':*p='y'; return;
        case 'Z':*p='z'; return;
      };
      return ;
      }
      
      void testtransform( std::string& word )
      {
      std::string word2=word; 
      time_t t;
      time_t t2;
      time(&t);
      std::cout << "testtransform: start " << "\n";
      int i=0;
      for(;i<9999999;i++) 
      {    word2=word;
          std::transform(word2.begin(), word2.end(), word2.begin(), ::tolower);
      }
      time(&t2);
      std::cout << word2 << "\n";
      std::cout << "testtransform: end " << i << ":"<< t2-t << "\n";
      }
      
      void testmytolower( std::string& word )
      {
      std::string word2=word; 
      time_t t;
      time_t t2;
      time(&t);
      std::cout << "testmytolower: start " << "\n";
      int i=0;
      for(;i<9999999;i++)
      {   word2=word;
          cstralgo::tolowerStr(word2);
      }
      time(&t2);
      std::cout << word2 << "\n";
      std::cout << "testmytolower: end " << i << ":"<< t2-t << "\n";
      }
      
      int main(int argc, char* argv[])
      {
         std::string word ="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
         word =word+word+word+word+word+word+word+word+word+word+word+word+word+word+word;
         testtransform( word);
         testmytolower( word);
         return 0;
      }
      

      我很高兴知道性能是否可以进一步提高。

      【讨论】:

      • 同样可以实现 toUpper
      【解决方案5】:

      我会使用 for 循环逐个字符地遍历整个字符串,将它们解析为一个函数以转换为大写。为了希望加快速度,我将在汇编中编写大写函数。应用程序的 C++ 组件如下所示:

      #include <iostream>
      #include <string>
      
      extern "C" char asm_capt(char x);
      
      using namespace std;
      
      int main(void)
      {
          string str;
          cin >> str;
          string tmp = str;
      
          for(int i=0; i<str.length(); i++){
      
              tmp.at(i) = asm_capt(str.at(i));
          }
      
      }
      

      现在是组装部分;我假设您使用的是 Windows 和 MASM 编译器。代码应保存在 .asm 源文件中,并使用 MASM 构建设置包含在项目中:

      .model flat
      .code
      _asm_capt proc
      
          mov rax, rcx
          cmp rax, 61h
          jl already_capt
      
          sub rax, 20h
          ret
      
          already_capt:
      
          ret
      
      _asm_capt endp
      end
      

      本质上,它检查字符(十六进制)是否小于 0x61,这意味着,如果你只使用字母,它已经大写了。否则,该值会减少 0x20,这会将其移动到小写等效值。 (参见 ASCII 表。)


      注意:默认情况下,返回参数存储在RAX寄存器中; C++ 传递给程序集的第一个参数存储在 RCX 中。

      【讨论】:

        【解决方案6】:

        所需的伪代码是:

        
        
        

        for(int i=0 to given_str.length)
        { upper[i]=(char*)given_str[i]-32; // in ascii tbl, any lower case char - upper case char=32
        }
        return upper;

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-06-28
          • 1970-01-01
          • 2015-11-27
          • 1970-01-01
          • 2015-02-28
          • 1970-01-01
          • 2014-06-23
          相关资源
          最近更新 更多