a

Câu hỏi Tôi có làm suy yếu hiệu quả của StringBuilder không?


Tôi đã bắt đầu sử dụng StringBuilder ưu tiên nối thẳng, nhưng có vẻ như nó thiếu một phương pháp quan trọng. Vì vậy, tôi đã tự thực hiện nó như một phần mở rộng:

public void Append(this StringBuilder stringBuilder, params string[] args)
{
    foreach (string arg in args)
        stringBuilder.Append(arg);
}

Điều này biến mess sau đây:

StringBuilder sb = new StringBuilder();
...
sb.Append(SettingNode);
sb.Append(KeyAttribute);
sb.Append(setting.Name);

Vào đây:

sb.Append(SettingNode, KeyAttribute, setting.Name);

tôi có thể dùng sb.AppendFormat("{0}{1}{2}",..., nhưng điều này dường như ít được ưa thích hơn, và vẫn còn khó đọc hơn. Phần mở rộng của tôi có phải là một phương pháp hay hay không, bằng cách nào đó làm suy yếu lợi ích của StringBuilder? Tôi không cố gắng tối ưu hóa sớm bất cứ thứ gì, vì phương pháp của tôi dễ hiểu hơn về tốc độ, nhưng tôi cũng muốn biết tôi không tự bắn mình vào chân.


52
2017-08-13 19:40


gốc


Nên chỉ ra rằng trừ khi bạn đã có một StringBuilder, chuỗi ký tự thẳng sẽ nhanh hơn. ví dụ. string s = "orig" + SettingNode + KeyAttribute + setting.Name; - hemp
@ Gai: Chỉ hơi. Nó được thoát khỏi cuộc gọi để Nối. Mọi thứ khác nên giống nhau. Và thats giả định không có append khác. - Devon_C_Miller
@ Gai: Điều gì về chuỗi s = string.Concat ("orig", SettingNode, KeyAttribute, setting.Name), tôi nghĩ rằng muốn được nhanh hơn bằng cách sử dụng "+" cho concat. - Daniel Lo Nigro
@ Daniel15: Có lẽ sẽ nhanh hơn nếu chúng không giống nhau. Trình biên dịch C # tối ưu hóa các chuỗi ký tự. Xem: bit.ly/94TRFG - hemp


Các câu trả lời:


Tôi thấy không có vấn đề với phần mở rộng của bạn. Nếu nó phù hợp với bạn thì mọi thứ đều tốt.

Bản thân tôi prefere:

sb.Append(SettingNode)
  .Append(KeyAttribute)
  .Append(setting.Name);

69
2017-08-13 19:45



+1 Làm theo cách này! Nếu họ viết nó để trả lại this, sau đó điều này hoàn toàn là những gì các nhà thiết kế dự định. - Merlyn Morgan-Graham
chất lỏng hoặc thông thạo - kenny
@ kenny là bạn hỏi những gì phong cách đó được gọi là? AFAIK, nó được gọi là "giao diện thông thạo". - Maxim Zaslavsky
Tôi thích chất lỏng từ tốt hơn !! xin vui lòng thay đổi;) - kenny
martinfowler.com/bliki/FluentInterface.html - palswim


Các câu hỏi như thế này luôn có thể được trả lời bằng một trường hợp thử nghiệm đơn giản.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace SBTest
{
    class Program
    {
        private const int ITERATIONS = 1000000;

        private static void Main(string[] args)
        {
            Test1();
            Test2();
            Test3();
        }

        private static void Test1()
        {
            var sw = Stopwatch.StartNew();
            var sb = new StringBuilder();

            for (var i = 0; i < ITERATIONS; i++)
            {
                sb.Append("TEST" + i.ToString("00000"),
                          "TEST" + (i + 1).ToString("00000"),
                          "TEST" + (i + 2).ToString("00000"));
            }

            sw.Stop();
            Console.WriteLine("Testing Append() extension method...");
            Console.WriteLine("--------------------------------------------");
            Console.WriteLine("Test 1 iterations: {0:n0}", ITERATIONS);
            Console.WriteLine("Test 1 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
            Console.WriteLine("Test 1 output length: {0:n0}", sb.Length);
            Console.WriteLine("");
        }

        private static void Test2()
        {
            var sw = Stopwatch.StartNew();
            var sb = new StringBuilder();

            for (var i = 0; i < ITERATIONS; i++)
            {
                sb.Append("TEST" + i.ToString("00000"));
                sb.Append("TEST" + (i+1).ToString("00000"));
                sb.Append("TEST" + (i+2).ToString("00000"));
            }

            sw.Stop();    
            Console.WriteLine("Testing multiple calls to Append() built-in method...");
            Console.WriteLine("--------------------------------------------");
            Console.WriteLine("Test 2 iterations: {0:n0}", ITERATIONS);
            Console.WriteLine("Test 2 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
            Console.WriteLine("Test 2 output length: {0:n0}", sb.Length);
            Console.WriteLine("");
        }

        private static void Test3()
        {
            var sw = Stopwatch.StartNew();
            var sb = new StringBuilder();

            for (var i = 0; i < ITERATIONS; i++)
            {
                sb.AppendFormat("{0}{1}{2}",
                    "TEST" + i.ToString("00000"),
                    "TEST" + (i + 1).ToString("00000"),
                    "TEST" + (i + 2).ToString("00000"));
            }

            sw.Stop();
            Console.WriteLine("Testing AppendFormat() built-in method...");
            Console.WriteLine("--------------------------------------------");            
            Console.WriteLine("Test 3 iterations: {0:n0}", ITERATIONS);
            Console.WriteLine("Test 3 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
            Console.WriteLine("Test 3 output length: {0:n0}", sb.Length);
            Console.WriteLine("");
        }
    }

    public static class SBExtentions
    {
        public static void Append(this StringBuilder sb, params string[] args)
        {
            foreach (var arg in args)
                sb.Append(arg);
        }
    }
}

Trên PC của tôi, đầu ra là:

Testing Append() extension method...
--------------------------------------------
Test 1 iterations: 1,000,000
Test 1 milliseconds: 1,080
Test 1 output length: 29,700,006

Testing multiple calls to Append() built-in method...
--------------------------------------------
Test 2 iterations: 1,000,000
Test 2 milliseconds: 1,001
Test 2 output length: 29,700,006

Testing AppendFormat() built-in method...
--------------------------------------------
Test 3 iterations: 1,000,000
Test 3 milliseconds: 1,124
Test 3 output length: 29,700,006

Vì vậy, phương pháp mở rộng của bạn chỉ chậm hơn một chút so với phương thức Append () và hơi nhanh hơn phương thức AppendFormat (), nhưng trong cả 3 trường hợp, sự khác biệt là hoàn toàn quá tầm thường để lo lắng. Vì vậy, nếu phương pháp mở rộng của bạn tăng cường khả năng đọc mã của bạn, hãy sử dụng nó!


32
2017-08-13 20:01



Tôi cảm thấy như ba "TEST" + i.ToString("00000") mỗi phụ thêm sẽ lùn thời gian cần thiết để gắn thêm vào StringBuilder, và có lẽ nên được chạy với chuỗi không đổi thay thế, chỉ để có được hình ảnh tốt hơn. - dlras2
Tuyệt vời thử nghiệm btw! - KP.
Bằng chứng - luôn luôn tốt. - ChrisF♦
+1 Nhận xét trước của tôi chỉ là cực kỳ khó - nhưng vâng, đó là một bài kiểm tra tốt! - dlras2
@ Daniel, tôi đã chỉ tạo ra các chuỗi động để tránh bất kỳ tối ưu hóa chuỗi interning có thể cung cấp cho. Không chắc chắn nếu nó thực sự ảnh hưởng đến thử nghiệm, nhưng tôi giả sử một người có thể chạy thử nghiệm với hằng số để kiểm tra lại. : P - Chris


Đó là một chút trên không tạo ra các mảng phụ, nhưng tôi nghi ngờ rằng nó rất nhiều. Bạn nên đo lường

Nếu nó chỉ ra rằng chi phí của việc tạo mảng chuỗi là quan trọng, bạn có thể giảm thiểu nó bằng cách có một số quá tải - một cho hai tham số, một cho ba, một cho bốn vv ... để chỉ khi bạn nhận được số tham số cao hơn (ví dụ sáu hoặc bảy) nó sẽ cần phải tạo mảng. Quá tải sẽ như thế này:

public void Append(this builder, string item1, string item2)
{
    builder.Append(item1);
    builder.Append(item2);
}

public void Append(this builder, string item1, string item2, string item3)
{
    builder.Append(item1);
    builder.Append(item2);
    builder.Append(item3);
}

public void Append(this builder, string item1, string item2,
                   string item3, string item4)
{
    builder.Append(item1);
    builder.Append(item2);
    builder.Append(item3);
    builder.Append(item4);
}

// etc

Và sau đó một quá tải cuối cùng bằng cách sử dụng params, ví dụ.

public void Append(this builder, string item1, string item2,
                   string item3, string item4, params string[] otherItems)
{
    builder.Append(item1);
    builder.Append(item2);
    builder.Append(item3);
    builder.Append(item4);
    foreach (string item in otherItems)
    {
        builder.Append(item);
    }
}

Tôi chắc chắn mong đợi những (hoặc chỉ là phương pháp mở rộng ban đầu của bạn) để được nhanh hơn so với sử dụng AppendFormat - sau đó, cần phải phân tích cú pháp chuỗi định dạng.

Lưu ý rằng tôi đã không thực hiện các quá tải này gọi cho nhau một cách đệ quy - I nghi ngờ chúng sẽ được gạch chân, nhưng nếu chúng không phải là chi phí của việc thiết lập một khung ngăn xếp mới, vv có thể kết thúc là đáng kể. (Chúng tôi giả định chi phí của mảng là đáng kể, nếu chúng ta có điều này đến nay.)


9
2017-08-13 19:49



Visual Studio dường như muốn sử dụng params string[] args thay vì string arg0, string arg1, v.v. Tôi có phải làm string arg0, string arg1, ..., params string[] moreArgs để có được điều này để làm việc? - dlras2
@Jon Không có hành vi phạm tội, nhưng nhiều tình trạng quá tải trông ghê tởm. Ít nhất làm cho nó một cuộc gọi hồi quy cho mỗi quá tải thêm, và tại sao không chỉ sử dụng params string [] args? - Lucas B
@ Lucas: Điểm của việc sử dụng một số tình trạng quá tải với nhiều tham số là để tránh các chi phí của mảng được tạo ra mỗi khi bạn sử dụng một phương pháp với một params tham số. Có, nó cũng có thể là quá mức cần thiết - nhưng đây là một câu hỏi hiệu suất, do đó câu trả lời. (Tôi đã nói lúc đầu rằng nó sẽ không có nhiều chi phí.) Như để thực hiện một cuộc gọi khác - Tôi đoán rằng sẽ không làm tổn thương nếu nó đang được inline ... nhưng nếu không thì đó là một khung ngăn xếp bổ sung mỗi lần. - Jon Skeet
@Daniel: Nhận xét của bạn không rõ ràng, nhưng có - bạn sẽ kết thúc bằng một cuộc gọi phương thức với nhiều string thông số để bắt đầu và một params string[] args ở cuối, sẽ chỉ được sử dụng nếu bạn có rất nhiều tham số. Nhưng như tôi đã nói khi bắt đầu câu trả lời của tôi, tôi nghi ngờ không có nhiều chi phí. Tôi sẽ chỉnh sửa câu trả lời của mình để làm rõ hơn. - Jon Skeet
@Jon - Xin lỗi vì nhận xét không rõ ràng; thật khó để phù hợp với mã đẹp trong một bình luận như thế. Tuy nhiên, từ giao diện của bạn, bạn đã hiểu tôi một cách hoàn hảo. - dlras2


Ngoài một chút chi phí, tôi không thấy bất kỳ vấn đề nào với nó. Chắc chắn dễ đọc hơn. Miễn là bạn đang vượt qua một số lượng hợp lý của params trong tôi không thấy vấn đề.


3
2017-08-13 19:44





Từ góc nhìn rõ ràng, tiện ích mở rộng của bạn là ok.

Nó có lẽ sẽ là tốt nhất để sử dụng định dạng .append (x) .append (y) .append (z) nếu bạn không bao giờ có nhiều hơn 5 hoặc 6 mục.

Bản thân StringBuilder sẽ chỉ giúp bạn đạt được hiệu năng nếu bạn đang xử lý hàng nghìn mục. Ngoài ra, bạn sẽ tạo mảng mỗi khi bạn gọi phương thức.

Vì vậy, nếu bạn đang làm nó cho rõ ràng, đó là ok. Nếu bạn đang làm nó cho hiệu quả, thì có thể bạn đang đi sai đường.


2
2017-08-13 20:08





Tôi sẽ không nói rằng bạn đang phá hoại hiệu quả của nó, nhưng bạn có thể làm điều gì đó không hiệu quả khi có một phương pháp hiệu quả hơn. AppendFormat là những gì tôi nghĩ rằng bạn muốn ở đây. Nếu chuỗi {0} {1} {2} đang được sử dụng liên tục quá xấu, tôi có xu hướng đặt các chuỗi định dạng của tôi trong các const ở trên, vì vậy giao diện sẽ ít nhiều giống với phần mở rộng của bạn.

sb.AppendFormat(SETTING_FORMAT, var1, var2, var3);

1
2017-08-13 19:45



Tôi không biết rằng AppendFormat là hiệu quả hơn. Chắc chắn, nó gọi Append một lần, nhưng dưới mui xe nó có thể đang làm StringBuildinger.Append(String.Format(...)), tức là một chuỗi phụ. Tất nhiên đó là phỏng đoán một trong hai cách cho đến khi ai đó disassembles và nhìn thấy những gì IL thực sự trông giống như - STW
@STW: Để chắc chắn tôi không thể nói như thể tôi biết, tôi sẽ chỉ nghĩ rằng một string.format mặc dù là hiệu quả hơn ba sb.Appends .. Không biết .. - Jimmy Hoffa
Tôi muốn được nghiêng để đi với AppendFormat, đặc biệt là nếu chuỗi đang được hiển thị cho người dùng; nếu chuỗi cần được định dạng lại (ví dụ: để bản địa hóa), bạn không cần phải thay đổi mã của mình. - Eric Brown
Bất cứ điều gì sử dụng chuỗi định dạng phải làm một số phân tích cú pháp thời gian chạy của chuỗi định dạng. Không có cách nào điều này sẽ hiệu quả hơn nhiều phần nối thêm. - bruceboughton


Tôi đã không thử nghiệm gần đây, nhưng trong quá khứ, StringBuilder đã thực sự chậm hơn so với kết nối chuỗi đồng bằng-vanilla ("this" + "that") cho đến khi bạn nhận được khoảng 7 concatenations.

Nếu đây là chuỗi nối mà không xảy ra trong một vòng lặp, bạn có thể muốn xem xét nếu bạn nên sử dụng StringBuilder ở tất cả. (Trong một vòng lặp, tôi bắt đầu lo lắng về việc phân bổ với chuỗi ký tự plain-vanilla, vì các chuỗi là không thay đổi.)


1
2017-08-14 01:12



1 cho nối, điểm tốt! - Lucas B


Thậm chí có khả năng nhanh hơn, bởi vì nó thực hiện nhiều nhất một bước tái phân bổ / sao chép, cho nhiều phụ thêm.

public void Append(this StringBuilder stringBuilder, params string[] args)
{
    int required = stringBuilder.Length;
    foreach (string arg in args)
        required += arg.Length;
    if (stringBuilder.Capacity < required)
        stringBuilder.Capacity = required;
    foreach (string arg in args)
        stringBuilder.Append(arg);
}

1
2017-08-12 22:27



Neat, nhưng một số timings để chứng minh nó sẽ được tốt đẹp. - Blorgbeard


Cuối cùng nó đi xuống mà một trong những kết quả trong việc tạo ra chuỗi ít hơn. Tôi có cảm giác rằng tiện ích mở rộng sẽ dẫn đến số chuỗi cao hơn bằng cách sử dụng định dạng chuỗi. Nhưng hiệu suất có thể sẽ không khác nhau.


0
2017-08-13 19:48





Chris,

Lấy cảm hứng từ câu trả lời của Jon Skeet (câu trả lời thứ hai), tôi hơi viết lại mã của bạn. Về cơ bản, tôi đã thêm phương thức TestRunner chạy hàm được truyền vào và báo cáo thời gian đã trôi qua, loại bỏ một mã dự phòng nhỏ. Không được tự mãn, mà là một bài tập lập trình cho bản thân mình. Tôi hy vọng nó hữu ích.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace SBTest
{
  class Program
  {
    private static void Main(string[] args)
    {
      // JIT everything
      AppendTest(1);
      AppendFormatTest(1);

      int iterations = 1000000;

      // Run Tests
      TestRunner(AppendTest, iterations);
      TestRunner(AppendFormatTest, iterations);

      Console.ReadLine();
    }

    private static void TestRunner(Func<int, long> action, int iterations)
    {
      GC.Collect();

      var sw = Stopwatch.StartNew();
      long length = action(iterations);
      sw.Stop();

      Console.WriteLine("--------------------- {0} -----------------------", action.Method.Name);
      Console.WriteLine("iterations: {0:n0}", iterations);
      Console.WriteLine("milliseconds: {0:n0}", sw.ElapsedMilliseconds);
      Console.WriteLine("output length: {0:n0}", length);
      Console.WriteLine("");
    }

    private static long AppendTest(int iterations)
    {
      var sb = new StringBuilder();

      for (var i = 0; i < iterations; i++)
      {
        sb.Append("TEST" + i.ToString("00000"),
                  "TEST" + (i + 1).ToString("00000"),
                  "TEST" + (i + 2).ToString("00000"));
      }

      return sb.Length;
    }

    private static long AppendFormatTest(int iterations)
    {
      var sb = new StringBuilder();

      for (var i = 0; i < iterations; i++)
      {
        sb.AppendFormat("{0}{1}{2}",
            "TEST" + i.ToString("00000"),
            "TEST" + (i + 1).ToString("00000"),
            "TEST" + (i + 2).ToString("00000"));
      }

      return sb.Length;
    }
  }

  public static class SBExtentions
  {
    public static void Append(this StringBuilder sb, params string[] args)
    {
      foreach (var arg in args)
        sb.Append(arg);
    }
  }
}

Đây là đầu ra:

--------------------- AppendTest -----------------------
iterations: 1,000,000
milliseconds: 1,274
output length: 29,700,006

--------------------- AppendFormatTest -----------------------
iterations: 1,000,000
milliseconds: 1,381
output length: 29,700,006

0
2017-08-18 18:42