Использовать существующий метод или переписать - вспомогательная библиотека - PullRequest
0 голосов
/ 30 января 2020

У меня есть библиотека классов, которая полна типичных методов, которые я часто использую, особенно в своей области. На данный момент большая часть этой библиотеки остается неизменной в течение многих лет. Больше добавлений, чем изменений.

В библиотеке есть много вызывающих методов внутри библиотеки - использующих себя.

С точки зрения полной оптимизации - потому что эта библиотека используется много, что делает больше смысла:

Используйте методы в lib или , переписывайте код внутри каждого метода , чтобы избежать вызова в другой метод. Какова стоимость вызова другого метода или, может быть, двух методов в глубину по сравнению с наличием кода в вызываемом методе.

Например, это иллюстрирует общий сценарий. Этот метод превращает то, что обычно является уродливым параметром URL (с кодированным html), в более простую, взломанную дату с тире. Таким образом, сотни пользователей могут вызывать его сотни раз, может быть, тысячи на странице (может быть, это не тривиальное количество раз?).

Причина, по которой я не думаю, что это предварительная оптимизация или микрооптимизация (поэтому я и спрашиваю), заключается в том, что, поскольку это библиотека, она используется многими приложениями на одном сервере с Для сотен пользователей «микро» экономия может действительно возрасти.

string GeturlDate(this DateTime date)
{
   return date.GetUrlDate(date, "-");
}
string GetUrlDate(this DateTime date, string delimiter)
{
   return DateHelper.GetUrlDate(date, delimiter);
}

string DateHelper.GetUrlDate(DateTime date, string delimiter)
{
   return string.format("{0}-{1}-{2}", date...);
}

В этом случае последний метод с помощью string.format может быть выполнен непосредственно в каждом из методов. Избегайте самого верхнего метода, в который входят два метода. Первые два - это методы расширения, а последний - прямой вызов.

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

Для сложных функций, которые могут легко привести к ошибкам, я бы оставил одно место.

РЕДАКТИРОВАТЬ : Для разъяснения концепции микрооптимизации и почему я думаю, что думать об этих вещах является правильным.

  • Часто вы не знаете, что у вас проблемы с производительностью - это не значит, что их нет - или тот, который только поднимает голову при определенных условиях
  • Есть случаи, когда микрооптимизация используется слишком быстро, потому что «это то, как я всегда делал это, и это работает (и нет никаких проблем), вместо того, чтобы подумать:« есть ли лучший способ сделать это »(возможно, при определенных условиях условия)
  • Маленькие победы имеют значение для горячих путей. Изменение, которое приводит к улучшению на 100 нс или к меньшему потреблению памяти, с коэффициентом 1К, 100К или 1М, может привести к заметной разнице.

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

В конечном счете, ответы на этот вопрос показывают, что помимо прочего, что делает компилятор и как написан сам BCL, нет причин для изменений.

Что является хорошей новостью: )

Ответы [ 3 ]

3 голосов
/ 30 января 2020

А) выявили ли вы фактическую проблему производительности, которую Б) можно отнести к этим конкретным методам, в частности к C) перегрузкам / оболочкам? Если нет, то это похоже на то, что угадывание решения для того, что вы угадываете, может быть проблемой.

Также взгляните, скажем, на метод string.Format(), который перегружает ; очевидно, что производительность будет критичной для тех, кто использует везде повсюду. NET, и все же все они обертывают вызов FormatHelper() вместо дублированных реализаций.

Имея это в виду, вы могли бы удалить один слой вызовов методов, изменив это ...

public static class DateExtensions
{
    public static string GetUrlDate(this DateTime date)
    {
        return date.GetUrlDate("-");
    }

    public static string GetUrlDate(this DateTime date, string delimiter)
    {
        return DateHelper.GetUrlDate(date, delimiter);
    }
}

... на это ...

public static class DateExtensions
{
    public static string GetUrlDate(this DateTime date)
    {
        return DateHelper.GetUrlDate(date, "-");
    }

    public static string GetUrlDate(this DateTime date, string delimiter)
    {
        return DateHelper.GetUrlDate(date, delimiter);
    }
}

... так что вместо одной перегрузки, вызывающей другую, они оба вызывают DateHelper.GetUrlDate(DateTime, String) напрямую. Однако перед тем, как вы go развернете свои методы-оболочки, рассмотрите этот BenchmarkDo tNet эталонный тест ...

using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;

namespace SO59976711
{
    public static class DateExtensions
    {
        public static string GetUrlDate(this DateTime date)
        {
            return date.GetUrlDate("-");
        }

        public static string GetUrlDate(this DateTime date, string delimiter)
        {
            return DateHelper.GetUrlDate(date, delimiter);
        }
    }

    public static class DateHelper
    {
        public static string GetUrlDate(DateTime date, string delimiter)
        {
            return string.Format("{0:yyyy}{1}{0:MM}{1}{0:dd}", date, delimiter);
        }
    }

    [SimpleJob(RuntimeMoniker.Net48)]
    [SimpleJob(RuntimeMoniker.NetCoreApp31)]
    public class DateFormattingBenchmarks
    {
        private static readonly DateTime TestDate = DateTime.Today;
        private const string TestDelimiter = "-";

        [Benchmark(Baseline = true)]
        public string String_Format()
        {
            // Use the same implementation as DateHelper.GetUrlDate() as a baseline
            return string.Format("{0:yyyy}{1}{0:MM}{1}{0:dd}", TestDate, TestDelimiter);
        }

        [Benchmark()]
        public string DateExtensions_GetUrlDate_DefaultDelimiter()
        {
            return TestDate.GetUrlDate();
        }

        [Benchmark()]
        public string DateExtensions_GetUrlDate_CustomDelimiter()
        {
            return TestDate.GetUrlDate(TestDelimiter);
        }

        [Benchmark()]
        public string DateHelper_GetUrlDate()
        {
            return DateHelper.GetUrlDate(TestDate, TestDelimiter);
        }
    }

    public static class Program
    {
        public static void Main()
        {
            BenchmarkDotNet.Running.BenchmarkRunner.Run<DateFormattingBenchmarks>();
        }
    }
}

... который дает эти результаты ...

// * Summary *

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7 CPU 860 2.80GHz (Nehalem), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.101
  [Host]     : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
  Job-RTUGNF : .NET Framework 4.8 (4.8.4075.0), X64 RyuJIT
  Job-NPEBBX : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT


|                                     Method |       Runtime |       Mean |   Error |  StdDev | Ratio |
|------------------------------------------- |-------------- |-----------:|--------:|--------:|------:|
|                              String_Format |      .NET 4.8 | 1,044.6 ns | 6.71 ns | 5.60 ns |  1.00 |
| DateExtensions_GetUrlDate_DefaultDelimiter |      .NET 4.8 | 1,040.0 ns | 4.55 ns | 4.26 ns |  1.00 |
|  DateExtensions_GetUrlDate_CustomDelimiter |      .NET 4.8 | 1,045.6 ns | 8.31 ns | 6.49 ns |  1.00 |
|                      DateHelper_GetUrlDate |      .NET 4.8 | 1,045.0 ns | 6.18 ns | 5.47 ns |  1.00 |
|                                            |               |            |         |         |       |
|                              String_Format | .NET Core 3.1 |   623.7 ns | 4.92 ns | 4.36 ns |  1.00 |
| DateExtensions_GetUrlDate_DefaultDelimiter | .NET Core 3.1 |   624.9 ns | 2.89 ns | 2.71 ns |  1.00 |
|  DateExtensions_GetUrlDate_CustomDelimiter | .NET Core 3.1 |   618.5 ns | 2.48 ns | 2.07 ns |  0.99 |
|                      DateHelper_GetUrlDate | .NET Core 3.1 |   621.4 ns | 2.97 ns | 2.48 ns |  1.00 |

Как видите, нет разницы в производительности между вызовом любого из ваших трех вспомогательных методов и самим string.Format(). Это означает, что вы должны реализовывать свои методы любым способом, который наиболее удобен для обслуживания с наименьшей избыточностью (т. Е. Сохранять их такими, какие они есть), потому что нет никакой выгоды, которую можно получить, дублируя код или «встроенные» встроенные методы.

1 голос
/ 30 января 2020

После того, как я исправил ваш код, я попытался использовать VS с включенной отладкой в ​​режиме Release и Goto Disassembly после достижения точки останова, используя [MethodImpl(MethodImplOptions.AggressiveInlining)] и [MethodImpl(MethodImplOptions.NoInlining)] и без подсказок компилятору.

С AggressiveInlining оказалось, что JIT встроил все вызовы от GetUrlDate() до DateHelper.GetUrlDate(DateTime date, string delimiter) в метод Main. Если не задано MethodImplOptions, метод Main, по-видимому, будет вызывать DateHelper.GetUrlDate напрямую. При NoInlining вызывался каждый метод, что также показывает IL.

Однако синхронизация показывает, что переключение с Release AggressiveInlining на Debug NoInlining - это всего лишь увеличение на 46 наносекунд на вызов, который кажется ужасно маленьким, чтобы беспокоиться о ручном встраивании вашего кода.

0 голосов
/ 30 января 2020

IMO - простите за наглость - на самом деле является идеальным примером ненужной микрооптимизации. Если вы на самом деле не видите проблемы с производительностью, и вы пишете разумный код (который вы есть), то аргумент для оптимизации слабый. Вложенность вызовов - это действительно незначительное соображение производительности, и для таких простых функций, как эта , вы не должны тратить время на размышления об IL! . Вы должны сравнить код, чтобы обосновать это, если вы действительно думаете, что есть проблема с производительностью. Я думаю, вы обнаружите, что вы тратите мозговые циклы.

...