String.format медленный, нужна более быстрая альтернатива - PullRequest
5 голосов
/ 21 марта 2011

Я надеялся получить совет о том, как ускорить следующую функцию.В частности, я надеюсь найти более быстрый способ преобразования чисел (в основном, удваивается, IIRC там есть один int) в строки для хранения в виде подэлементов Listview.В настоящее время эта функция обрабатывает 16 заказов за 9 секунд!Абсолютно безумный, особенно если учесть, что за исключением вызова обработки 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 для красивого вывода, я бы хотел иметь возможностьзагружать список заказов в течение моей жизни больше, так что, если есть альтернатива string.format, которая значительно быстрее, но без наворотов, я полностью за это.


Редактировать: Спасибовсе люди, предложившие класс myOrder, могли бы использовать методы получения, а не хранить переменные, как я изначально думал.Я проверил это и, конечно же, это стало причиной моего замедления.Хотя у меня нет доступа к классу, чтобы изменить его, я смог добавить к вызову метода заполнение myOrders и скопировать каждую переменную в список в рамках того же вызова, а затем использовать этот список при заполнении моего ListView.Население почти мгновенно.Еще раз спасибо.

Ответы [ 5 ]

3 голосов
/ 21 марта 2011

Мне трудно поверить в то, что простые строки. Вызовы формата вызывают ваши проблемы с медлительностью - обычно это очень быстрые вызовы, особенно для простых простых, таких как большинство ваших.

Но одна вещь, которая может дать вам несколько микросекунд ...

Заменить

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

с

myOrder.MinQuantityToBuy.ToString("g")

Это будет работать, когда вы выполняете прямой формат одного значения, но не подходит для более сложных вызовов.

0 голосов
/ 03 мая 2011

Я рад, что вы решили свою проблему.Однако я провел небольшой рефакторинг для вашего метода и придумал следующее:

    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. Создайте новое свойство «DateTimeWhenIssued» для MyOrder, которое будет возвращать тип данных DateTime.Это следует использовать вместо объединения двух строк (DateWhenIssued и TimeWhenIssued) и последующего их синтаксического анализа в DateTime;
  3. Переименовать ListViewItem, поскольку это встроенный класс;
  4. ListViewItem должен иметь новый конструктор длялогическое «IsByOrder»: var listItem = new ListViewItem (myOrder.IsBuyOrder).Вместо строки «Buy» или «Sell» * массив строк
  5. «mySubItems» следует заменить классом для лучшей читаемости и расширяемости;
  6. Наконец, foreach (MyOrder myOrder в myOrders) можно заменить циклом «for», так как вы все равно используете счетчик.Кроме того, циклы for тоже быстрее.

Надеюсь, вы не возражаете против моих предложений и того, что они выполнимы в вашей ситуации.

PS.Вы используете универсальные массивы?Свойство ListViewItem.SubItems может иметь значение public List<string> SubItems { get; set; };

0 голосов
/ 21 марта 2011

Ничего себе.Я чувствую себя немного глупо сейчас.Я часами бился головой о стену, пытаясь понять, почему простая операция на струне займет так много времени.MarketOrders - это (я думал) массив myOrders, который заполняется явным вызовом метода, который строго ограничен тем, сколько раз в секунду он может быть запущен.У меня нет доступа к этому коду для проверки, но я предполагал, что myOrders были простыми структурами с переменными-членами, которые присваивались при заполнении MarketOrders, поэтому вызовы string.format просто воздействовали бы на существующие данные.Прочитав все ответы, которые указывают на доступ к данным myOrder как на виновника, я начал думать об этом и понял, что MarketOrders, скорее всего, является просто индексом, а не массивом, а информация myOrder читается по требованию.Поэтому каждый раз, когда я вызываю операцию с одной из ее переменных, я вызываю метод медленного поиска, ожидаю, когда он снова сможет работать, возвращаюсь к моему методу, вызываю следующий поиск и т. Д. Неудивительно, что это происходит вечно.

Спасибо за все ответы.Я не могу поверить, что это не произошло со мной.

0 голосов
/ 21 марта 2011

Это определенно не строка. Формат, который замедляет вас.Подозреваю, что свойство обращается к myOrder.

В одном из вызовов формата попробуйте объявить несколько локальных переменных и установить их в свойства, которые вы пытаетесь отформатировать, а затем передать эти локальные переменные в строку string.Format и retime,Вы можете обнаружить, что ваш string.Format теперь работает с молниеносной скоростью, как и должен.

Теперь доступ к свойствам обычно не требует много времени для запуска.Тем не менее, я видел некоторые классы, в которых регистрируется каждый доступ к свойству (для аудита).Проверьте, так ли это, и если какая-то операция удерживает доступ к вашему свойству от немедленного возврата.

Если есть какая-либо операция, удерживающая доступ к свойству, попробуйте поставить эти операции в очередь (например, поставить в очередь вызовы журналирования) ииметь фоновый поток, выполнить их.Немедленно верните доступ к свойству.

Кроме того, никогда не помещайте медленно работающий код (например, сложные вычисления) в свойство accesser / getter, а также код, имеющий побочные эффекты.Люди, использующие этот класс, не будут знать, что он будет медленным (поскольку большинство обращений к свойствам происходит быстро) или имеет побочные эффекты (поскольку большинство обращений к свойствам не имеют побочных эффектов).Если доступ медленный, переименуйте доступ к функции GetXXX ().Если у него есть побочные эффекты, назовите метод как-то, что передает этот факт.

0 голосов
/ 21 марта 2011

Я поместил все вызовы string.format в цикл и смог выполнить их все миллион раз за секунду, поэтому ваша проблема не в string.format ... это где-то еще в вашем коде.

Возможно, некоторые из этих свойств имеют логику в своих методах получения? Сколько раз вы получаете, если вы закомментируете весь код для просмотра списка?

...