Как определить присутствует ли операция boxing в c#?

В общем есть такой код:

public class AccountProcessor
{
    // ToDo Реализовать без копирования и боксинга
    public decimal Calculate(BankAccount bankAccount)
    {
        return CalculateOperation(bankAccount.LastOperation) +
               CalculateOperation(bankAccount.PreviousOperation) +
               CalculateOperation1(bankAccount.LastOperation) +
               CalculateOperation1(bankAccount.PreviousOperation) +
               CalculateOperation2(bankAccount.LastOperation) +
               CalculateOperation2(bankAccount.PreviousOperation) +
               CalculateOperation3(bankAccount.LastOperation) +
               CalculateOperation3(bankAccount.PreviousOperation) +
               CalculateOperation3(bankAccount)
               +
               CalculateOperation(bankAccount.LastOperation) +
               CalculateOperation(bankAccount.PreviousOperation) +
               CalculateOperation1(bankAccount.LastOperation) +
               CalculateOperation1(bankAccount.PreviousOperation) +
               CalculateOperation2(bankAccount.LastOperation) +
               CalculateOperation2(bankAccount.PreviousOperation) +
               CalculateOperation3(bankAccount.LastOperation) +
               CalculateOperation3(bankAccount.PreviousOperation) +
               CalculateOperation3(bankAccount)
               +
               CalculateOperation(bankAccount.LastOperation) +
               CalculateOperation(bankAccount.PreviousOperation) +
               CalculateOperation1(bankAccount.LastOperation) +
               CalculateOperation1(bankAccount.PreviousOperation) +
               CalculateOperation2(bankAccount.LastOperation) +
               CalculateOperation2(bankAccount.PreviousOperation) +
               CalculateOperation3(bankAccount.LastOperation) +
               CalculateOperation3(bankAccount.PreviousOperation) +
               CalculateOperation3(bankAccount);
    }

    private decimal CalculateOperation(BankOperation bankOperation)
    {
        // Some calculation code
        return bankOperation.OperationInfo0;
    }

    private decimal CalculateOperation1(BankOperation bankOperation)
    {
        // Some calculation code
        return bankOperation.OperationInfo1;
    }

    private decimal CalculateOperation2(BankOperation bankOperation)
    {
        // Some calculation code
        return bankOperation.OperationInfo2;
    }

    private decimal CalculateOperation3(ITotalAmount bankOperation)
    {
        // Some calculation code
        return bankOperation.TotalAmount;
    }
}


public struct BankAccount : ITotalAmount
{
    public decimal TotalAmount { get; set; }
    public BankOperation LastOperation { get; set; }
    public BankOperation PreviousOperation { get; set; }
}

public interface ITotalAmount
{
    decimal TotalAmount { get; set; }
}

public struct BankOperation : ITotalAmount
{
    public decimal TotalAmount { get; set; }

    public long Rubles { get; set; }

    public short Kopeks { get; set; }

    public long RublesBeforeOperation { get; set; }

    public short KopeksBeforeOperation { get; set; }

    public long RublesAfterOperation { get; set; }

    public short KopeksAfterOperation { get; set; }

    public long OperationInfo0 { get; set; }
    public long OperationInfo1 { get; set; }
    public long OperationInfo2 { get; set; }
    public long OperationInfo3 { get; set; }
    public long OperationInfo4 { get; set; }
    public long OperationInfo5 { get; set; }
    public long OperationInfo6 { get; set; }
    public long OperationInfo7 { get; set; }
    public long OperationInfo8 { get; set; }
    public long OperationInfo9 { get; set; }
    public long OperationInfo10 { get; set; }
    public long OperationInfo11 { get; set; }
    public long OperationInfo12 { get; set; }
    public long OperationInfo13 { get; set; }
    public long OperationInfo14 { get; set; }
    public long OperationInfo15 { get; set; }
    public long OperationInfo16 { get; set; }
    public long OperationInfo17 { get; set; }
    public long OperationInfo18 { get; set; }
    public long OperationInfo19 { get; set; }
    public long OperationInfo20 { get; set; }
    public long OperationInfo21 { get; set; }
    public long OperationInfo22 { get; set; }
    public long OperationInfo23 { get; set; }
    public long OperationInfo24 { get; set; }
    public long OperationInfo25 { get; set; }
    public long OperationInfo26 { get; set; }
    public long OperationInfo27 { get; set; }
    public long OperationInfo28 { get; set; }
    public long OperationInfo29 { get; set; }
    public long OperationInfo30 { get; set; }
    public long OperationInfo31 { get; set; }
    public long OperationInfo32 { get; set; }
    public long OperationInfo33 { get; set; }
    public long OperationInfo34 { get; set; }
    public long OperationInfo35 { get; set; }
    public long OperationInfo36 { get; set; }
    public long OperationInfo37 { get; set; }
    public long OperationInfo38 { get; set; }
    public long OperationInfo39 { get; set; }
    public long OperationInfo40 { get; set; }
    public long OperationInfo41 { get; set; }
    public long OperationInfo42 { get; set; }
    public long OperationInfo43 { get; set; }
    public long OperationInfo44 { get; set; }
    public long OperationInfo45 { get; set; }
    public long OperationInfo46 { get; set; }
    public long OperationInfo47 { get; set; }
    public long OperationInfo48 { get; set; }
    public long OperationInfo49 { get; set; }
    public long OperationInfo50 { get; set; }
    public long OperationInfo51 { get; set; }
    public long OperationInfo52 { get; set; }
    public long OperationInfo53 { get; set; }
    public long OperationInfo54 { get; set; }
    public long OperationInfo55 { get; set; }
    public long OperationInfo56 { get; set; }
    public long OperationInfo57 { get; set; }
    public long OperationInfo58 { get; set; }
    public long OperationInfo59 { get; set; }
}

Нужно было реализовать метод CalculatePerformed, который делал бы то же самое, но без копирования и боксинга. Вот мое решение:

public decimal CalculatePerformed(in BankAccount bankAccount)
{
    return CalculateLastOperationPerformed(in bankAccount) +
           CalculatePreviousOperationPerformed(in bankAccount) +
           CalculateLastOperationPerformed1(in bankAccount) +
           CalculatePreviousOperationPerformed1(in bankAccount) +
           CalculateLastOperationPerformed2(in bankAccount) +
           CalculatePreviousOperationPerformed2(in bankAccount) +
           CalculateLastOperationPerformed3(in bankAccount) +
           CalculatePreviousOperationPerformed3(in bankAccount) +
           CalculateAccountOperationPerformed3(in bankAccount)
           +
           CalculateLastOperationPerformed(in bankAccount) +
           CalculatePreviousOperationPerformed(in bankAccount) +
           CalculateLastOperationPerformed1(in bankAccount) +
           CalculatePreviousOperationPerformed1(in bankAccount) +
           CalculateLastOperationPerformed2(in bankAccount) +
           CalculatePreviousOperationPerformed2(in bankAccount) +
           CalculateLastOperationPerformed3(in bankAccount) +
           CalculatePreviousOperationPerformed3(in bankAccount) +
           CalculateAccountOperationPerformed3(in bankAccount)
           +
           CalculateLastOperationPerformed(in bankAccount) +
           CalculatePreviousOperationPerformed(in bankAccount) +
           CalculateLastOperationPerformed1(in bankAccount) +
           CalculatePreviousOperationPerformed1(in bankAccount) +
           CalculateLastOperationPerformed2(in bankAccount) +
           CalculatePreviousOperationPerformed2(in bankAccount) +
           CalculateLastOperationPerformed3(in bankAccount) +
           CalculatePreviousOperationPerformed3(in bankAccount) +
           CalculateAccountOperationPerformed3(in bankAccount);
}

private decimal CalculateLastOperationPerformed(in BankAccount bankAccount)
{
    // Some calculation code
    return bankAccount.LastOperation.OperationInfo0;
}

private decimal CalculatePreviousOperationPerformed(in BankAccount bankAccount)
{
    // Some calculation code
    return bankAccount.PreviousOperation.OperationInfo0;
}

private decimal CalculateLastOperationPerformed1(in BankAccount bankAccount)
{
    // Some calculation code
    return bankAccount.LastOperation.OperationInfo1;
}

private decimal CalculatePreviousOperationPerformed1(in BankAccount bankAccount)
{
    // Some calculation code
    return bankAccount.PreviousOperation.OperationInfo1;
}

private decimal CalculateLastOperationPerformed2(in BankAccount bankAccount)
{
    // Some calculation code
    return bankAccount.LastOperation.OperationInfo2;
}

private decimal CalculatePreviousOperationPerformed2(in BankAccount bankAccount)
{
    // Some calculation code
    return bankAccount.PreviousOperation.OperationInfo2;
}

private decimal CalculateLastOperationPerformed3(in BankAccount bankAccount)
{
    // Some calculation code
    return bankAccount.LastOperation.TotalAmount;
}

private decimal CalculatePreviousOperationPerformed3(in BankAccount bankAccount)
{
    // Some calculation code
    return bankAccount.PreviousOperation.TotalAmount;
}

private decimal CalculateAccountOperationPerformed3(in BankAccount bankAccount)
{
    // Some calculation code
    return bankAccount.TotalAmount;
}

Как проверить что нет операции боксинга? Только через IL код или еще как нибудь можно? И да, на сколько мое решение хорошее, можно ли было сделать лучше и как? Бенчмарк:

// Результаты
// |             Method |       Mean |    Error |  StdDev | Rank |   Gen0 | Allocated |
// |------------------- |-----------:|---------:|--------:|-----:|-------:|----------:|
// | CalculatePerformed |   833.5 ns |  7.99 ns | 7.48 ns |    1 |      - |         - |
// |          Calculate | 2,021.7 ns | 10.17 ns | 8.49 ns |    2 | 3.2120 |    6720 B |

Ответы (1 шт):

Автор решения: InterceptorTSK

Боксинг влечёт за собой объект. По определению. А если боксинга нет - ну значит и объекта нет.

Пользуйте множество методов из System.GС и всякие связанные с ним GCMemoryInfo. Это всё можно запустить перед тестом, и после теста. И получить абсолютно все данные по количеству объектов, сколько они занимают памяти и так далее. Если у вас боксинг идёт - так у вас количество объектов будет увеличиваться и GC это покажет, и использование памяти этими объектами тоже покажет, и так далее. А если объектов нет - ну значит и боксинга нет.

И на самом деле это всё нужно смотреть в отладчиках, они в общем то простые и показывают объекты. Вплоть до какого нибудь кучерявого вин-дбг, и даже там можно увидеть хип, и как он растёт (если растёт).

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

Ваше решение (в первом простейшем грубом приближении) тогда хорошее, когда оно исполняется как минимум в два раза быстрее чем было, иначе оптимизации мало чего дают. И второе, может быть более существенное - это ваше решение потребляет сильно меньше памяти на куче, т.е. вы не плодите объекты. Условно говоря у вас был расход на куче 4Кб, стал 32б - вот это хорошо, это замечательно. А совсем хорошо когда строго 0б, это когда вы кучу не используете вообще.

Совсем дикий способ, и он не всегда показывает то что вам нужно. В идеале вы запускаете приложение, и посмотрите на память в банальном диспетчере задач. Вы теребонькаете ваше приложение, и память ДОЛЖНА СТОЯТЬ. Т.е. она вообще не должна меняться. Условно говоря как было например 8 232 Кб - вот так оно и должно быть. Вы пользуете вашу программу, а память стоит. Вообще не меняется. Никак. Это то, к чему вы должны прийти. В идеале.

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

→ Ссылка