【问题标题】:Ways to improve string memory allocation改善字符串内存分配的方法
【发布时间】:2020-02-01 20:55:03
【问题描述】:

这个问题比实际更理论,但仍然如此。

我一直在寻找机会从字符串内存分配的角度改进以下代码:

/* Output for n = 3:
*
*  '  #'
*  ' ##'
*  '###'
*
*/
public static string[] staircase(int n) {
    string[] result = new string[n];

    for(var i = 0; i < result.Length; i++) {
        var spaces = string.Empty.PadLeft(n - i - 1, ' ');
        var sharpes = string.Empty.PadRight(i + 1, '#');

        result[i] = spaces + sharpes;
    }

    return result;
}

PadHelper 是方法,最终每次迭代都会在后台调用两次。

所以,如果我错了,请纠正我,但似乎每次迭代至少分配了 3 次内存。

任何代码改进都将受到高度赞赏。

【问题讨论】:

  • 这似乎有点挑错了树,因为这种方法的最大胜利将是完全消除string[],以及所有用于生成它的东西。如果直接用作输出,则最终结果是打印 9 个字符和 3 个换行符(嗯,X 和 Y 取决于 n),这完全可以在没有任何显式分配的情况下完成。 (或者,更实际一点,因为打印单个字符效率很低,所有行只有一个字符串缓冲区。)
  • 也许你可以分配一个带有n - 1空格的字符串 n在循环外锐化,然后为每个i返回一个子字符串,从偏移量i.
  • 是的,.NET 中的字符串是不可变的。因此,任何字符串操作都会导致新的分配。 StringBuilder 类可能在这里有所帮助
  • StringBuilder 在字符串分配方面始终是一个答案;我敢肯定你知道,所以显然你想要别的东西。好吧,由于您的字符串长度都相同,您可以声明一个 char[] 数组,每次都填充它(只需在每次迭代中更改两个相邻元素),然后使用 string(char[]) constructor
  • 如果你让函数返回IEnumerable&lt;string&gt;yield return,那么你甚至不需要字符串数组。

标签: c# .net string optimization stringbuilder


【解决方案1】:

怎么样:

result[i] = new string('#',i).PadLeft(n)

?

请注意,这个 still 在内部分配了两个字符串,但老实说,我不认为这是一个问题。垃圾收集器会替你处理。

【讨论】:

  • 它似乎取决于字符串的构造函数实现,但看起来很合理。谢谢你。
  • @FSou1 “它似乎取决于字符串的构造函数实现” 是的 - 这是一件坏事吗?构造函数实现已记录在案,因此不存在更改的风险。
【解决方案2】:

StringBuilder 在字符串分配方面始终是一个答案;我敢肯定你知道,所以显然你想要别的东西。好吧,由于您的字符串长度相同,您可以声明一个 char[] 数组,每次都填充它(每次迭代只需更改一个数组元素),然后使用 string(char[]) constructor

public static string[] staircase(int n)
{
    char[] buf = new char[n];
    string[] result = new string[n];

    for (var i = 0; i < n - 1; i++)
    {
        buf[i] = ' ';
    }

    for (var i = 0; i < n; i++)
    {
        buf[n - i - 1] = '#';
        result[i] = new string(buf);
    }

    return result;
}

【讨论】:

    【解决方案3】:

    您可以从一个包含所有空格和您将需要的所有Sharpes的字符串开始,然后从中获取子字符串,如下所示:

    public string[] Staircase2()
    {
        string allChars = new string(' ', n - 1) + new string('#', n); // n-1 spaces + n sharpes
    
        string[] result = new string[n];
        for (var i = 0; i < result.Length; i++)
            result[i] = allChars.Substring(i, n);
    
        return result;
    }
    

    我使用BenchmarkDotNetStaircase1(您的原始方法)与Staircase2(我上面的方法)从n=2n=8 进行比较,请参见下面的结果。

    这表明Staircase2 总是更快(参见Mean 列),并且它从n=3 开始分配更少的字节。

    |     Method | n |        Mean |      Error |     StdDev | Allocated |
    |----------- |-- |------------:|-----------:|-----------:|----------:|
    
    | Staircase1 | 2 |   229.36 ns |  4.3320 ns |  4.0522 ns |      92 B |
    | Staircase2 | 2 |    92.00 ns |  0.7200 ns |  0.6735 ns |     116 B |
    
    | Staircase1 | 3 |   375.06 ns |  3.3043 ns |  3.0908 ns |     156 B |
    | Staircase2 | 3 |   114.12 ns |  2.8933 ns |  3.2159 ns |     148 B |
    
    | Staircase1 | 4 |   507.32 ns |  3.8995 ns |  3.2562 ns |     236 B |
    | Staircase2 | 4 |   142.78 ns |  1.4575 ns |  1.3634 ns |     196 B |
    
    | Staircase1 | 5 |   650.03 ns | 15.1515 ns | 25.7284 ns |     312 B |
    | Staircase2 | 5 |   169.25 ns |  1.9076 ns |  1.6911 ns |     232 B |
    
    | Staircase1 | 6 |   785.75 ns | 16.9353 ns | 15.8413 ns |     412 B |
    | Staircase2 | 6 |   195.91 ns |  2.9852 ns |  2.4928 ns |     292 B |
    
    | Staircase1 | 7 |   919.15 ns | 11.4145 ns | 10.6771 ns |     500 B |
    | Staircase2 | 7 |   237.55 ns |  4.6380 ns |  4.9627 ns |     332 B |
    
    | Staircase1 | 8 | 1,075.66 ns | 26.7013 ns | 40.7756 ns |     620 B |
    | Staircase2 | 8 |   255.50 ns |  2.6894 ns |  2.3841 ns |     404 B |
    

    这并不意味着 Staircase2 绝对是最好的,但肯定有比原来更好的方法。

    【讨论】:

    • 您的方法似乎缺少int n 参数。
    • 由于 BenchmarkDotNet 的工作原理,最简单的方法是在方法之外声明变量 n 并让 BM.Net 为所有被测方法设置其值,在这种情况下,值从 2 到8. 这适用于我的方法Staircase1Staircase2
    【解决方案4】:

    您可以使用 Linq Select 方法来预测您想要的结果。例如,像这样:

    public static string[] staircase(int n) {
        return Enumerable.Range(1, n).Select(i => new string('#', i).PadLeft(n)).ToArray();
    }
    

    使用int 数组的替代方法:

    public static string[] staircase(int n) {
        return (new int[n]).Select((x,i) => new string('#', i+1).PadLeft(n)).ToArray();
    }
    

    HTH

    【讨论】:

    • 这正是D Stanley's answer,仅适用于整个数组。
    • 说实话,单线解决方案总是看起来很优雅,所以我喜欢它 :)
    • @GSerg 这个想法是使用 Linq 演示投影Select
    • @FSou1 的想法是生成一个整数数组,然后您可以将其投影到填充字符串。字符串填充不是 IMO 的问题,通过最小化对象的实例化和枚举,您会受益更多。
    猜你喜欢
    • 1970-01-01
    • 2011-03-22
    • 2021-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    相关资源
    最近更新 更多