整数到字符串优化函数

Integer to string optimized function?

本文关键字:函数 优化 字符串 整数      更新时间:2023-10-16

我目前有一个函数可以将无符号整数转换为字符串(我需要一个适用于__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;
}

有什么方法可以优化这个代码吗?

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

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

请记住,在进行优化时,最重要的始终是评测。

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

如果幸运的话,您将在字符串的"短字符串优化"缓冲区大小内。如果没有,则会产生动态内存分配,这可能至少比转换代码慢一个数量级。因此,首先,去掉std::string,并添加对确定合适的原始缓冲区大小的支持。

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

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


尽管如此,请记住测量

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

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

例如:

基数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

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

#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的技术可以更有效地优化它(使用作为所提供的基础的幂的基础)。