我期待“截断” 0从长期价值。
例如,对于长期价值“1234560000”,我想放弃最后四0(123456),也需要知道有多少0的已被丢弃。
我可以用10点%的操作实现这一点:
void Main()
{
Console.WriteLine(Truncate(1234560000));
}
public static long Truncate(long mantissa)
{
int droppedZeros = 0;
while (mantissa % 10 == 0)
{
mantissa /= 10;
droppedZeros++;
}
return mantissa;
}
这段代码得到获取调用数百万次,是性能的关键,我寻找各种方法来提高性能来达到同样的无模(可在此与位移位做什么?)。
每个请求,我添加一些基准号码包括与已知的恒定展示模运算的相对开销编译时执行分割的基准:
Method | Mean | Error | StdDev | Median | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
---------------- |----------:|----------:|----------:|----------:|------------:|------------:|------------:|--------------------:|
DivideNoModulo | 1.863 ms | 0.0431 ms | 0.1272 ms | 1.855 ms | - | - | - | - |
ModuloBasic | 21.342 ms | 0.8776 ms | 2.5876 ms | 20.813 ms | - | - | - | - |
DivisionBasic | 18.159 ms | 1.7218 ms | 5.0768 ms | 15.937 ms | - | - | - | - |
DivisionSmarter | 7.510 ms | 0.5307 ms | 1.5649 ms | 7.201 ms | - | - | - | - |
ModuloSmarter | 8.517 ms | 0.1673 ms | 0.2886 ms | 8.531 ms | - | - | - | - |
StringTruncate | 42.370 ms | 1.7358 ms | 5.1181 ms | 40.566 ms | 1000.0000 | - | - | 8806456 B |
基准代码:
[SimpleJob(RunStrategy.ColdStart, 1)]
[MemoryDiagnoser]
public class EncoderBenchmark
{
private long _mantissa;
[Benchmark]
public void DivideNoModulo()
{
for (var i = 0; i < 100000; i++)
{
_mantissa = 12345600000000;
_mantissa /= 100000000;
}
}
[Benchmark]
public void ModuloBasic()
{
for (var i = 0; i < 100000; i++)
{
_mantissa = 12345600000000;
while (_mantissa % 10 == 0)
{
_mantissa /= 10;
}
}
}
[Benchmark]
public void DivisionBasic()
{
for (var i = 0; i < 100000; i++)
{
_mantissa = 12345600000000;
for (;;)
{
long temp = _mantissa / 10;
if (temp * 10 != _mantissa)
break;
_mantissa = temp;
}
}
}
[Benchmark]
public void DivisionSmarter()
{
for (var i = 0; i < 100000; i++)
{
_mantissa = 12345600000000;
for (; ; )
{
long temp = _mantissa / 1000000;
if (temp * 1000000 != _mantissa)
break;
_mantissa = temp;
}
for (; ; )
{
long temp = _mantissa / 10;
if (temp * 10 != _mantissa)
break;
_mantissa = temp;
}
}
}
[Benchmark]
public void ModuloSmarter()
{
for (var i = 0; i < 100000; i++)
{
_mantissa = 12345600000000;
while (_mantissa % 1000000 == 0)
{
_mantissa /= 1000000;
}
while (_mantissa % 10 == 0)
{
_mantissa /= 10;
}
}
}
[Benchmark]
public void StringTruncate()
{
for (var i = 0; i < 100000; i++)
{
_mantissa = 12345600000000;
_mantissa = long.Parse(_mantissa.ToString().TrimEnd('0'));
}
}
}
有点快替换“%”与“*”
public static long T(long mantissa)
{
if (mantissa == 0)
return 0;
int droppedZeros = 0;
for (; ; )
{
long temp = mantissa / 1000000;
if (temp * 1000000 != mantissa)
break;
mantissa = temp;
droppedZeros += 6;
}
for (; ; )
{
long temp = mantissa / 1000;
if (temp * 1000 != mantissa)
break;
mantissa = temp;
droppedZeros += 3;
}
for (; ; )
{
long temp = mantissa / 10;
if (temp * 10 != mantissa)
break;
mantissa = temp;
droppedZeros++;
}
return mantissa;
}
你会很不可能得到它与位移位有效地工作,因为这是理想的由两个大国,它的10
不是一个分裂。
一种改进,在那里你可以经常得到有很多尾随零的数目可能性,是使用多个循环做的工作在更大的块,如:
if (mantissa != 0) {
while (mantissa % 1000000 == 0) {
mantissa /= 1000000;
droppedZeros += 6;
}
while (mantissa % 1000 == 0) {
mantissa /= 1000;
droppedZeros += 3;
}
while (mantissa % 10 == 0) {
mantissa /= 10;
droppedZeros++;
}
}
这通常会导致被执行的指令较少,但与所有的优化,措施,不用猜!这可能是添加的代码的复杂性是不值得你(如果有的话)的收益。
请注意,我也抓住了mantissa == 0
情况,因为这将导致你原来代码中的无限循环。
你可能要考虑另一种可能性是,如果你正在做此操作同一项目多次。例如,假设你有一个整数的集合,并且,每次需要对它们进行处理的一个时间,你必须脱掉并计算尾随零。
在这种情况下,你实际上可以将它们存储在不同的方式。例如,考虑(伪代码)结构:
struct item:
int originalItem
int itemWithoutZeroes
int zeroCount
基本上,只要您第一次收到的项目(如1234560000
),您可以立即将其转换为结构一次,只有一次:
{ 1234560000, 123456, 4 }
这提供了零剥离项目的缓存版本,所以你永远不必再计算。
所以,如果你想剥离尾数,你只需要使用item.itemWithoutZeros
。如果你想输出原始形式的数量,你可以使用item.originalItem
。而且,如果你想零的数量,使用item.zeroCount
。
显然,这会占用更多的存储空间,但你经常会发现优化是一个时间/空间权衡。
如下更新您的Truncate
逻辑。
public static long Truncate(long mantissa)
{
if (mantissa == 0)
return 0;
var mantissaStr = mantissa.ToString();
var mantissaTruncated = mantissaStr.TrimEnd('0');
int droppedZeros = mantissaStr.Length - mantissaTruncated.Length;
return Convert.ToInt64(mantissaTruncated);
}