【问题标题】:String.format slow, need faster alternativeString.format 慢,需要更快的替代方案
【发布时间】:2011-03-21 03:54:57
【问题描述】:

我希望就如何加快以下功能获得一些建议。具体来说,我希望找到一种更快的方法将数字(主要是双精度数,IIRC 里面有一个 int)转换为字符串以存储为 Listview 子项。就目前而言,这个函数需要 9 秒来处理 16 个订单!绝对疯了,特别是考虑到除了处理 DateTimes 的调用之外,这一切都只是字符串转换。

我认为列表视图项的实际显示速度很慢,所以我做了一些研究,发现将所有子项添加到数组中并使用 Addrange 比一次添加一项要快得多。我实施了更改,但没有更好的速度。

然后我在每条线路周围添加了一些秒表,以准确缩小导致减速的原因;不出所料,对 datetime 函数的调用是最慢的,但我很惊讶地看到 string.format 调用也非常慢,考虑到它们的数量,占了我大部分时间。

    private void ProcessOrders(List<MyOrder> myOrders)
    {
        lvItems.Items.Clear();
        marketInfo = new MarketInfo();
        ListViewItem[] myItems = new ListViewItem[myOrders.Count];
        string[] mySubItems = new string[8];
        int counter = 0;
        MarketInfo.GetTime();
        CurrentTime = MarketInfo.CurrentTime;
        DateTime OrderIssueDate = new DateTime();

        foreach (MyOrder myOrder in myOrders)
        {
            string orderIsBuySell = "Buy";
            if (!myOrder.IsBuyOrder)
                orderIsBuySell = "Sell";
            var listItem = new ListViewItem(orderIsBuySell);

            mySubItems[0] = (myOrder.Name);
            mySubItems[1] = (string.Format("{0:g}", myOrder.QuantityRemaining) + "/" + string.Format("{0:g}", myOrder.InitialQuantity));
            mySubItems[2] = (string.Format("{0:f}", myOrder.Price));
            mySubItems[3] = (myOrder.Local);
            if (myOrder.IsBuyOrder)
            {
                if (myOrder.Range == -1)
                    mySubItems[4] = ("Local");
                else
                    mySubItems[4] = (string.Format("{0:g}", myOrder.Range));
            }
            else
                mySubItems[4] = ("N/A");
            mySubItems[5] = (string.Format("{0:g}", myOrder.MinQuantityToBuy));
            string IssueDateString = (myOrder.DateWhenIssued + " " + myOrder.TimeWhenIssued);
            if (DateTime.TryParse(IssueDateString, out OrderIssueDate))
                mySubItems[6] = (string.Format(MarketInfo.ParseTimeData(CurrentTime, OrderIssueDate, myOrder.Duration)));
            else
                mySubItems[6] = "Error getting date";
            mySubItems[7] = (string.Format("{0:g}", myOrder.ID));
            listItem.SubItems.AddRange(mySubItems);
            myItems[counter] = listItem;
            counter++;

        }
        lvItems.BeginUpdate();
        lvItems.Items.AddRange(myItems.ToArray());
        lvItems.EndUpdate();
    }

这是来自示例运行的时间数据:
0: 166686
1:264779
2:273716
3:136698
4:587902
5:368816
6:955478
7:128981

其中数字等于数组的索引。与这些相比,所有其他行的刻度都非常低,可以忽略不计。

虽然我希望能够使用 string.format 的数字格式来获得漂亮的输出,但我希望能够在我的一生中加载更多的订单列表,所以如果有字符串的替代方案。格式更快,但没有花里胡哨,我完全赞成。


编辑:感谢所有建议 myOrder 类可能使用 getter 方法而不是像我最初想的那样实际存储变量的人。我检查了一下,果然,这就是我放慢速度的原因。尽管我无权访问该类来更改它,但我能够搭载方法调用来填充 myOrders 并将每个变量复制到同一调用中的列表中,然后在填充我的 Listview 时使用该列表。现在几乎立即填充。再次感谢。

【问题讨论】:

  • 我将其重新标记为 .net 和 c#,我一眼就看出来了。希望没问题。
  • 您期望的基准时间是多少?我非常怀疑使用String.Format() 会导致 很多时间,它应该可以忽略不计。我会仔细看看你的MarketInfo 方法调用。在我看来,您将要做一些 I/O,我怀疑这就是所有减速的原因。
  • 您可能希望在代码示例中包含 MyOrder 类,或者至少明确 MyOrder 的属性是否只是小数/整数值,或者它们是否是可能在返回之前进行计算的 getter 方法一个值(因为这可能会影响对时间的解释)。
  • 我实际上怀疑ListView 操作是缓慢的部分。如果您注释掉引用 listItem 的行,您的函数会加速吗?

标签: c# .net string string.format


【解决方案1】:

我很难相信简单的 string.Format 调用会导致您的缓慢问题 - 它通常是一个非常快的调用,尤其是对于像您大多数人这样简单的调用。

但有一件事可能会给您带来几微秒的时间......

替换

string.Format("{0:g}", myOrder.MinQuantityToBuy)

myOrder.MinQuantityToBuy.ToString("g")

当您执行单个值的直接格式时,这将起作用,但对于更复杂的调用没有任何好处。

【讨论】:

  • 是的,这也是我首先想到的。使用这个快捷方式可以避免每次都解析格式字符串,这在我的机器上节省了 200ns。它可能不是几微秒,但可能就足够了。
  • 我没有进行基准测试,但这似乎工作得更快。它减少了我在一个非常快速的应用程序中遇到的一些瓶颈。谢谢!
【解决方案2】:

我将所有 string.format 调用放入一个循环中,并且能够在一秒钟内将它们全部运行 100 万次,所以您的问题不在于 string.format...它在您的代码中的其他地方。

也许其中一些属性在其 getter 方法中有逻辑?如果你注释掉列表视图的所有代码,你会得到什么样的时间?

【讨论】:

    【解决方案3】:

    绝对不是 string.Format 让你慢下来。怀疑来自 myOrder 的属性访问。

    在其中一个格式调用中,尝试声明一些局部变量并将它们设置为您尝试格式化的属性,然后将这些局部变量传递给 yoru string.Format 并重新计时。您可能会发现您的 string.Format 现在以应有的速度运行。

    现在,属性访问通常不需要太多时间来运行。但是,我见过一些记录每个属性访问的类(用于审计跟踪)。检查是否是这种情况,以及是否某些操作阻止您的属性访问立即返回。

    如果有一些操作持有属性访问权限,请尝试将这些操作排队(例如,将日志调用排队)并让后台线程执行它们。立即返回属性访问权限。

    此外,切勿将运行缓慢的代码(例如复杂的计算)放入属性访问器/getter,也不要将具有副作用的代码。使用该类的人不会意识到它会很慢(因为大多数属性访问速度很快)或有副作用(因为大多数属性访问没有副作用)。如果访问速度很慢,请将访问重命名为 GetXXX() 函数。如果它有副作用,请将该方法命名为传达这一事实的名称。

    【讨论】:

      【解决方案4】:

      哇。我现在觉得有点傻。我花了好几个小时把头撞在墙上,试图弄清楚为什么一个简单的字符串操作会花这么长时间。 MarketOrders 是(我认为)一个 myOrders 数组,它通过显式调用一个方法来填充,该方法在每秒可以运行的次数方面受到严格限制。我无法访问该代码来检查,但我一直假设 myOrders 是简单的结构,其成员变量是在填充 MarketOrders 时分配的,因此 string.format 调用只会对现有数据起作用。在阅读所有指向 myOrder 数据访问作为罪魁祸首的回复时,我开始思考它并意识到 MarketOrders 可能只是一个索引,而不是一个数组,并且 myOrder 信息正在按需读取。因此,每次我在其中一个变量上调用操作时,我都会调用慢速查找方法,等待它有资格再次运行,返回我的方法,调用下一个查找等。难怪它会永远持续下去。

      感谢所有回复。我不敢相信这没有发生在我身上。

      【讨论】:

        【解决方案5】:

        很高兴您的问题得到解决。但是我对你的方法做了一个小的重构并想出了这个:

            private void ProcessOrders(List<MyOrder> myOrders)
            {
                lvItems.Items.Clear();
                marketInfo = new MarketInfo();
                ListViewItem[] myItems = new ListViewItem[myOrders.Count];
                string[] mySubItems = new string[8];
                int counter = 0;
                MarketInfo.GetTime();
                CurrentTime = MarketInfo.CurrentTime;
                // ReSharper disable TooWideLocalVariableScope
                DateTime orderIssueDate;
                // ReSharper restore TooWideLocalVariableScope
        
                foreach (MyOrder myOrder in myOrders)
                {
                    string orderIsBuySell = myOrder.IsBuyOrder ? "Buy" : "Sell";
                    var listItem = new ListViewItem(orderIsBuySell);
        
                    mySubItems[0] = myOrder.Name;
                    mySubItems[1] = string.Format("{0:g}/{1:g}", myOrder.QuantityRemaining, myOrder.InitialQuantity);
                    mySubItems[2] = myOrder.Price.ToString("f");
                    mySubItems[3] = myOrder.Local;
        
                    if (myOrder.IsBuyOrder)
                        mySubItems[4] = myOrder.Range == -1 ? "Local" : myOrder.Range.ToString("g");
                    else
                        mySubItems[4] = "N/A";
        
                    mySubItems[5] = myOrder.MinQuantityToBuy.ToString("g");
        
                    // This code smells:
                    string issueDateString = string.Format("{0} {1}", myOrder.DateWhenIssued, myOrder.TimeWhenIssued);
                    if (DateTime.TryParse(issueDateString, out orderIssueDate))
                        mySubItems[6] = MarketInfo.ParseTimeData(CurrentTime, orderIssueDate, myOrder.Duration);
                    else
                        mySubItems[6] = "Error getting date";
        
                    mySubItems[7] = myOrder.ID.ToString("g");
        
                    listItem.SubItems.AddRange(mySubItems);
                    myItems[counter] = listItem;
                    counter++;
                }
                lvItems.BeginUpdate();
                lvItems.Items.AddRange(myItems.ToArray());
                lvItems.EndUpdate();
            }
        

        这个方法应该进一步重构:

        1. 考虑到控制反转 (IoC) 并使用依赖注入 (DI) 删除外部依赖项;
        2. 为将返回 DateTime 数据类型的 MyOrder 创建新属性“DateTimeWhenIssued”。应该使用它而不是连接两个字符串(DateWhenIssued 和 TimeWhenIssued),然后将它们解析为 DateTime;
        3. 重命名 ListViewItem,因为这是一个内置类;
        4. ListViewItem 应该有一个用于布尔“IsByOrder”的新构造函数:var listItem = new ListViewItem(myOrder.IsBuyOrder)。而不是字符串“Buy”或“Sell”;
        5. “mySubItems”字符串数组应替换为一个类以获得更好的可读性和可扩展性;
        6. 最后,当您使用计数器时,foreach(myOrders 中的 MyOrder myOrder)可以替换为“for”循环。除了“for”循环也更快。

        希望您不介意我的建议,并且它们在您的情况下是可行的。

        PS。你在使用通用数组吗? ListViewItem.SubItems 属性可以是 public List&lt;string&gt; SubItems { get; set; };

        【讨论】:

          猜你喜欢
          • 2011-11-13
          • 2019-04-22
          • 1970-01-01
          • 2018-12-11
          • 2012-07-05
          • 2012-01-23
          • 2013-07-13
          • 2011-02-27
          相关资源
          最近更新 更多