【问题标题】:Integer to string optimized function?整数到字符串优化函数?
【发布时间】:2014-04-07 18:21:44
【问题描述】:

我目前有这个函数可以将无符号整数转换为字符串(我需要一个适用于__uint128_t 等非标准类型的函数):

#include <iostream>
#include <algorithm>
#include <string>

template <typename UnsignedIntegral>
std::string stringify(UnsignedIntegral number, const unsigned int base)
{
    static const char zero = '0';
    static const char alpha = 'A';
    static const char ten = 10;
    std::string result;
    char remainder = 0;
    do {
        remainder = number%base;
        result += (remainder < ten) ? (zero+remainder) : (alpha+remainder-ten);
        number /= base;
    } while (number > 0);
    std::reverse(std::begin(result), std::end(result));
    return result;
}

int main()
{
   std::cout<<stringify(126349823, 2)<<std::endl;
   return 0;
}

有没有办法优化这段代码?

【问题讨论】:

  • 一种方法是将一组可能的“数字”存储为一个静态数组(0 到 9 后跟 A 到 Z)并对其进行索引,而不是使用条件来决定是否使用数字或字母。此外,对于常用的碱基,如 2、8、10 和 16,您可以为这些碱基提供具有优化算法的模板特化。
  • 这看起来更适合codereview,因为它是关于改进有效的代码,而不是修复无效的代码。
  • 嗯...sprintf?您真的希望通过优化获得超过 0.5% 的收益吗?
  • @Angew 你不是说不满足性能要求算作不工作吗?
  • 在@dvnrrs 之后,当基数是 2 的幂时,使用按位运算而不是非常昂贵的 % 和 /。你也可以通过使用恒等式 A % B = A - (A / B) * B 来避免使用 %(你用 % 换*)。而当已知底数不超过 10 时,就没有十六进制数字了。

标签: c++ string optimization c++11 integer


【解决方案1】:

您可能想阅读 Alexei Alexandrescu 的这篇文章,他在其中谈到了使用(固定基数)int 到字符串转换的低级优化:

https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920

请记住,优化时最重要的事情始终是分析。

【讨论】:

    【解决方案2】:

    一个简单的事情是避免多个堆分配,这可以通过result.reserve(CHAR_BIT * sizeof(Integral))(可能的最大字符串为基数2)或先将字符串构建到本地数组中然后从中创建std::string来完成。即便如此,我同意@SebastianRedl;您无法在没有测量的情况下进行优化。此外,您的代码没有考虑负数。

    【讨论】:

      【解决方案3】:

      如果幸运的话,您将处于字符串的“短字符串优化”缓冲区大小内。如果不是,那么您会招致 动态内存分配,这可能至少比转换代码慢一个数量级。所以首先,去掉那个std::string,并添加对确定合适的原始缓冲区大小的支持。

      完成后,摆脱由选择运算符引起的分支。表查找可能会更快(或不会)。但也可以使用位技巧,例如通过右移将小负数转换为全1,然后将其用作掩码。

      最后,您可以从提供的缓冲区的末尾直接向后构建结果,而不是反转结果,并生成作为函数结果开始的指针。


      说了这么多,记得MEASURE

      对于逻辑上不能明显比原来差的优化,例如上述,测量可能比简单地进行优化编码更多的工作。但是当你完成了显而易见的事情并且你有兴趣获得最后一点性能时,测量是必要的。此外,对于大多数程序员来说,测量是必要的,只是为了不把时间浪费在完全不必要的优化上,或者引入新的低效率。

      【讨论】:

        【解决方案4】:

        您正在寻求一种优化代码的方法。确实有一种比纯逐位转换更快的方法:您可以处理数字组,即在所需基数的幂的基数中。

        例如:

        基数 2 -> 基数 256(一次 8 位)

        以 8 为基数 -> 以 512 为基数(一次 3 个八进制数字)

        以 10 为底 -> 以 100 为底(一次 2 个十进制数字)

        以 16 为基数 -> 以 256 为基数(一次 2 个十六进制数字)

        您需要将数字组合的表示预先制表为短 ASCII 字符串。并且您将需要添加对高位数字的特殊处理以避免或撤消前导零。

        OctalStrings[]= { "000", "001", "002" ... }
        

        但主循环将保持形式:

        do
          Q= N / Base
          R= N - Q * Base
          N= Q
          Insert(Strings[R])
        while N>0
        

        或者,对于二进制基数:

        do
          R= N & (Base - 1)
          N= N >> LgBase
          Insert(Strings[R])
        while N>0
        

        您还可以通过预先计算底的所有幂并使用商/余数直接从左到右进行转换。

        Base100Powers[]= { 1, 100, 10000, 1000000... }
        
        do
          Q= N / Powers[k]
          N= N - Powers[k] * Q
          Append(Strings[Q])
          k--
        while k>0
        

        【讨论】:

          【解决方案5】:

          我认为这是一个更高效的版本,我刚刚编写了代码:

          #include <iostream>
          #include <type_traits>
          #include <algorithm>
          #include <string>
          #include <array>
          
          template <bool Upper = true, 
                    typename Char = char,
                    Char Zero = '0',
                    Char Nine = '9',
                    Char Alpha = Upper ? 'A' : 'a',
                    Char Zeta = Upper ? 'Z' : 'z',
                    Char One = 1,
                    Char Ten = Nine-Zero+One,
                    Char Size = (Nine-Zero+One)+(Zeta-Alpha+One),
                    typename... Types, 
                    class = typename std::enable_if<
                    (std::is_convertible<Char, char>::value) && 
                    (sizeof...(Types) == Size)>::type>
          constexpr std::array<char, Size> alphabet(const Types&... values)
          {
              return {{values...}};
          }
          
          template <bool Upper = true, 
                    typename Char = char,
                    Char Zero = '0',
                    Char Nine = '9',
                    Char Alpha = Upper ? 'A' : 'a',
                    Char Zeta = Upper ? 'Z' : 'z',
                    Char One = 1,
                    Char Ten = Nine-Zero+One,
                    Char Size = (Nine-Zero+One)+(Zeta-Alpha+One),
                    typename... Types, 
                    class = typename std::enable_if<
                    (std::is_convertible<Char, char>::value) && 
                    (sizeof...(Types) < Size)>::type>
          constexpr std::array<char, Size> alphabet(Types&&... values)
          {
              return alphabet<Upper, Char, Zero, Nine, Alpha, Zeta, One, Ten, Size>
                     (std::forward<Types>(values)..., 
                      Char(sizeof...(values) < Ten ? Zero+sizeof...(values)
                                                   : Alpha+sizeof...(values)-Ten));
          }
          
          template <typename Integral,
                    Integral Base = 10,
                    Integral Zero = 0,
                    Integral One = 1, 
                    Integral Value = ~Zero,
                    class = typename std::enable_if<
                    (std::is_convertible<Integral, int>::value) && 
                    (Value >= Zero) && 
                    (Base > One)>::type>
          constexpr Integral digits()
          {
              return (Value != ~Zero)+
                     (Value > Zero ? digits<Integral, Base, Zero, One, Value/Base>()
                                   : Zero);
          }
          
          template <bool Upper = true,
                    typename Integral,
                    std::size_t Size = digits<Integral, 2>()>
          std::string stringify(Integral number, const std::size_t base)
          {
              static constexpr auto letters = alphabet<Upper>();
              std::array<char, Size+1> string = {};
              std::size_t i = 0;
              do {
                  string[Size-i++] = letters[number%base];
              } while ((number /= base) > 0);
              return &string[Size+1-i];
          }
          
          int main()
          {
              std::cout<<stringify(812723U, 16)<<std::endl;
              return 0;
          }
          

          使用 Yves Daoust 的技术可以更有效地优化它(使用作为所提供基础的力量的基础)。

          【讨论】:

          • “你认为”?计时数字,否则我不相信你。
          猜你喜欢
          • 1970-01-01
          • 2017-02-05
          • 1970-01-01
          • 2014-01-26
          • 2011-02-16
          • 1970-01-01
          • 1970-01-01
          • 2020-03-14
          • 1970-01-01
          相关资源
          最近更新 更多