String.format很慢,需要更快的替代方案

时间:2011-03-21 03:54:57

标签: c# .net string string.format

我希望就如何加快以下功能提出一些建议。具体来说,我希望找到一种更快的方法来将数字(大多数是双数,IIRC那里有一个int)转换为存储为Listview子项的字符串。目前,此功能需要9秒才能处理16个订单!绝对是疯了,特别是考虑到除了处理DateTimes的调用之外,它只是字符串转换。

我认为这是listview项目的实际显示速度很慢,所以我做了一些研究,发现将所有子项添加到数组并使用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时使用该列表。现在几乎立即填充。再次感谢。

5 个答案:

答案 0 :(得分:3)

我发现很难相信简单的string.Format调用会导致你的慢速问题 - 它通常是一个非常快速的调用,特别是像大多数你这样的简单调用。

但有一件事可能会给你一些微秒......

替换

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

myOrder.MinQuantityToBuy.ToString("g")

当您执行单个值的直接格式时,这将起作用,但对于更复杂的调用则无效。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

绝对不是string.Format会减慢你的速度。怀疑来自myOrder的财产访问。

在其中一个格式调用中,尝试声明一些局部变量并将其设置为您尝试格式化的属性,然后将这些局部变量传递给yoru string.Format和retime。你可能会发现你的string.Format现在以闪电般的速度运行。

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

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

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

答案 3 :(得分:0)

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

感谢所有回复。我无法相信没有发生在我身上。

答案 4 :(得分:0)

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

    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. 为MyOrder创建将返回DateTime数据类型的新属性“DateTimeWhenIssued”。应该使用它来代替连接两个字符串(DateWhenIssued和TimeWhenIssued),然后将它们解析为DateTime;
  3. 重命名ListViewItem,因为这是一个内置类;
  4. ListViewItem应该有一个新的构造函数用于布尔“IsByOrder”:var listItem = new ListViewItem(myOrder.IsBuyOrder)。而不是字符串“买”或“卖”;
  5. “mySubItems”字符串数组应替换为类,以提高可读性和可扩展性;
  6. 最后,foreach(myOrder中的MyOrder myOrder)可以替换为“for”循环,因为你正在使用计数器。除了“for”循环也更快。
  7. 希望你不介意我的建议,并且在你的情况下它们是可行的。

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