возможные альтернативы reflection

Lord_Max

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

Public Sub DoWork(table As IEnumerable(Of IEnumerable(Of IComparable
Dim columnMax As New List(Of IComparable)
Dim index As Integer
For Each row In table
index = 0
For Each item In row
If columnMax.Count <= index Then columnMax.Insert(index, item)
If columnMax(index).CompareTo(item) < 0 Then columnMax(index) = item
index += 1
Next
Next
For Each max In columnMax
Console.WriteLine(max)
Next

таблица имеет столбцы постоянного типа, но разного между собой:

DoWork( _
New List(Of IEnumerable(Of IComparable{ _
New List(Of IComparable{2.1, 3, "ZZ"} _
New List(Of IComparable{-5.4, 9, "zz"}) _
}

к процедуре претензий нет, но беда в том, что данные как-то удобнее хранить в IEnumerable(Of T и их для анализа приходится сначала переводить в таблицу, а это куча одноразового кода
хочется переделать её, чтобы она получала IEnumerable каких-то объектов

Public Sub DoWork(Of Tdata As IEnumerable(Of T

и, вместо столбцов, итерировать эти объекты по IComparable Member'ам
собственно вопрос - можно ли это сделать без Reflection (фрэймворк 4.0, VB)?

Dasar

> Public Sub DoWork(Of Tdata As IEnumerable(Of T
добавить к T требование, чтобы он реализовывал интерфейс, который возвращает IEnumerable<IComparable>

Lord_Max

не, всё не настолько запущено
ценность процедуры в том, что ей не важно какие данные обрабатывать, главное чтобы было похоже на таблицу из бд
хочется мочь применять её к любым IEnumerable(Of T хоть к List(Of MyClass хоть к Process.GetProcesses, хоть к MyForm.Controls
т.е. если сделать IEnumerable(Of IComparable) со всеми IComparable полями/свойствами T, то задача решена
пока что получился костыль, через который DoWork может работать с Fields/Properties как столбцами:

Public Class ObjectReader2

Public _Names As String = {}
Public _Types As Type = {}
Public _Fields As Func(Of Object, Object) = {}
Public _Proprs As Func(Of Object, Object, Object) = {}
Public _Parents As Integer = {}

Default ReadOnly Property Value(Item As Object, Index As Integer) As Object
Get
Dim Parent As Integer = _Parents(Index)
If Parent = -1 Then Return Item
Dim ParentValue As Object = Value(Item, Parent)
If _Proprs(Index) IsNot Nothing Then Return _Proprs(IndexParentValue, Nothing)
If _Fields(Index) IsNot Nothing Then Return _Fields(IndexParentValue)
Return Nothing
End Get
End Property

Private Sub Init(_Name As String, _Type As Type, _Field As Func(Of Object, Object _Propr As Func(Of Object, Object, Object _Parent As Integer, ExcludeSet As HashSet(Of Type
If (_Type.GetInterfaces.Contains(GetType(IEnumerable And Not _Type.GetInterfaces.Contains(GetType(IComparable Or ExcludeSet.Contains(_Type) Then Exit Sub
Dim Index As Integer = _Names.Length
Array.Resize(_Names, Index + 1)
Array.Resize(_Types, Index + 1)
Array.Resize(_Fields, Index + 1)
Array.Resize(_Proprs, Index + 1)
Array.Resize(_Parents, Index + 1)
_Names(Index) = _Name
_Types(Index) = _Type
_Fields(Index) = _Field
_Proprs(Index) = _Propr
_Parents(Index) = _Parent
If _Type.GetInterfaces.Contains(GetType(IComparable Then Exit Sub
Dim NExcludeSet As New HashSet(Of TypeExcludeSet)
NExcludeSet.Add(_Type)
For Each NField In _Type.GetFields
Init(_Name & "." & NField.Name, NField.FieldType, AddressOf NField.GetValue, Nothing, Index, NExcludeSet)
Next
For Each NPropr In _Type.GetProperties
Init(_Name & "." & NPropr.Name, NPropr.PropertyType, Nothing, AddressOf NPropr.GetValue, Index, NExcludeSet)
Next
End Sub

Sub New(ItemType As Type)
Init(ItemType.Name, ItemType, Nothing, Nothing, -1, New HashSet(Of Type{GetType(Object)}
End Sub

End Class

но он мало того что ужасен, так ещё и в 9 раз тормознее
direct get worktime: 722.9172
reflection worktime: 6290.8154
естественная мысль - хорошо бы его сделать более типизованым, чтобы хотя бы не box/unbox'илось, но мб оно как-то и по другому можно?

Dasar

ценность процедуры в том, что ей не важно какие данные обрабатывать, главное чтобы было похоже на таблицу из бд
Сделать DoWork следующего вида:

void DoWork<TRow>(IEnumerable<TRow> table, Func<TRow, IEnumerable<IComparable>> rowValues)

использование

DoWork(items, item=>new IComparable[]{item.Age, item.Weight});

Dasar

reflection worktime: 6290.8154
Чтобы reflection быстро работал необходимо:
- (хуже) или кэшировать результаты получения GetFields/GetProperties
- (лучше) или кэшировать сгенеренные скомпиленные лямбды, обращающиеся к полям и свойствам

Dasar

Public _Parents As Integer = {}
Parents что такое? И в чём его необходимость в этом коде?

Lord_Max

> - (хуже) или кэшировать результаты получения GetFields/GetProperties
это сделал в ObjectReader3, результат не однозначный, всего строк много, но отдельно взятая row используется 2 раза, от этого кэш где-то даже ухудшает
> - (лучше) или кэшировать сгенеренные скомпиленные лямбды, обращающиеся к полям и свойствам
начал делать в ObjectReader4, но подумал, что это оверкил

Lord_Max

> Parents что такое? И в чём его необходимость в этом коде?
рекурсия, для не IComparable fields/properties находит их IComparable fields/properties
т.е. чтобы для MyClass сделать не только row из его IComparable полей:
{MyClass.MyField1, MyClass.MyField2, ...},
но ещё и добавить туда же IComparable поля от MyClass.MyNestedClass:
{MyClass.MyField1, MyClass.MyField2, ..., MyClass.MyNestedClass.MyField1, ...}
ps:
отправная точка это http://stackoverflow.com/questions/852181/c-printing-all-pro...
и ObjectDumper из C:\Program Files (x86)\Microsoft Visual Studio 9.0\Samples\1033\CSharpSamples.zip

Dasar

При оптимальном коде производительность от использования reflection меняется следующим образом

00:00:00.2082508 0.7 на входе сразу массив значений
00:00:00.2729971 1 структура преобразуется в массив
00:00:00.3158256 1.1 структура преобразуется через reflection, скомпиленный в lambda
00:00:00.8434679 3.1 структура преобразуется через reflection


public static void DoWork<TRow>(IEnumerable<TRow> rows, Func<TRow, IEnumerable<IComparable>> rowValues)
{
IComparable[] maxes = null;

foreach (var row in rows)
{
if (maxes == null)
maxes = rowValues(row).ToArray;
else
{
foreach (var pair in rowValues(row).Selectvalue, i) => new { value, i }
{
if (pair.value.CompareTo(maxes[pair.i]) > 0)
maxes[pair.i] = pair.value;
}
}
}
Console.WriteLine(string.Join(", ", (object[])maxes;
}
public static void DoWork_Reflection<TRow>(IEnumerable<TRow> rows)
{
var fields = typeof(TRow).GetProperties.Where(field => typeof(IComparable).IsAssignableFrom(field.PropertyType.ToArray;
var p = Expression.Parameter(typeof(TRow;
var d1 = Expression.Lambda<Func<TRow, IComparable>>(Expression.Convert(Expression.Property(p, fields[0] typeof(IComparable p);
DoWork(rows, row => fields.Select(field => (IComparable)field.GetValue(row, null;
}
public static void DoWork_LambdaReflection<TRow>(IEnumerable<TRow> rows)
{
var fields = typeof(TRow)
.GetProperties
.Where(field => typeof(IComparable).IsAssignableFrom(field.PropertyType
.Select(property =>
{
var p = Expression.Parameter(typeof(TRow;
return Expression.Lambda<Func<TRow, IComparable>>(Expression.Convert(Expression.Property(p, property typeof(IComparable p).Compile;
}
)
.ToArray;
DoWork(rows, row => fields.Select(field => field(row;
}

static void Method1
{
DoWork(Enumerable.Repeat(new IComparable[] { 0, "zz", 1.0 }, 1000000 vals => vals);
}
static void Method2
{
DoWork(Enumerable.Repeat(new { x = 0, y = "zz", z = 1.0 }, 1000000 row => new IComparable[]{row.x, row.y, row.z});
}
static void Method3
{
var rows = Enumerable.Repeat(new { x = 0, y = "zz", z = 1.0 }, 1000000);

DoWork_Reflection(rows);
}
static void Method4
{
var rows = Enumerable.Repeat(new { x = 0, y = "zz", z = 1.0 }, 1000000);

DoWork_LambdaReflection(rows);
}

public static void Execute
{
var methods = new Action[]{Method1, Method2, Method3, Method4};
foreach (var method in methods)
{
method;
}
const int tryCount = 10;
foreach (var method in methods)
{
var watch = new System.Diagnostics.Stopwatch;
watch.Start;
for (var i = 0; i < tryCount; ++i)
{
method;
}
watch.Stop;
Console.WriteLine("{0}", TimeSpan.FromTicks(watch.Elapsed.Ticks / tryCount;
}
}
}

Dasar

Если использовать магию и убрать boxing, то всё станет в 2 раза быстрее

00:00:00.2039813 сразу массив
00:00:00.0995472 anti-boxing магия


public static void DoWork<TRow>(IEnumerable<TRow> rows, IColumnMax<TRow>[] maxes)
{

var isInited = false;
foreach (var row in rows)
{
if (isInited)
{
foreach (var column in maxes)
column.Compare(row);
}
else
{
foreach (var column in maxes)
column.Init(row);
isInited = true;
}
}
Console.WriteLine(string.Join(", ", maxes.Select(column => column.Max;
}
static void Method5
{
var rows = Enumerable.Repeat(new { x = 0, y = "zz", z = 1.0 }, 1000000);

DoWork(rows,
new[]
{
ToColumnMax(rows, item => item.x, (v1, v2)=> v1.CompareTo(v2
ToColumnMax(rows, item => item.y, (v1, v2)=> v1.CompareTo(v2
ToColumnMax(rows, item => item.z, (v1, v2)=> v1.CompareTo(v2
});
}
static IColumnMax<TRow> ToColumnMax<T, TRow>(IEnumerable<TRow> rows, Func<TRow, T> value, Func<T, T, int> comparer) where T:IComparable
{
return new ColumnMax<T, TRow>(new Column<T, TRow>(value, comparer;
}
interface IColumnMax<TRow>
{
void Init(TRow row);
void Compare(TRow row);
object Max { get; }
}
class ColumnMax<T, TRow>:IColumnMax<TRow> where T:IComparable
{
public ColumnMax(Column<T, TRow> column)
{
this.Column = column;
}
public readonly Column<T, TRow> Column;

public T Max;
public void Init(TRow row)
{
Max = Column.Value(row);
}
public void Compare(TRow row)
{
var v = Column.Value(row);
if (Column.Comparer(v, Max) > 0)
Max = v;
}
object IColumnMax<TRow>.Max {get {return Max;}}
}
class Column<T, TRow>
{
public Column(Func<TRow, T> value, Func<T, T, int> comparer)
{
this.Value = value;
this.Comparer = comparer;
}
public readonly Func<TRow, T> Value;
public readonly Func<T, T, int> Comparer;
}

Lord_Max

спасибо, очень помог
не много обыдлокодил твой код (см ниже хочу обратить внимание на Classic vs NotSoClassic:

Classic : 590.0 ms
NotSoClassic : 3430.5 ms
DelegateOnArrays : 3522.2 ms
DelegateOnObjects : 4432.1 ms
Reflection : 25119.6 ms
LambdaReflection : 6673.5 ms
AntiBoxing : 2260.7 ms

различие Classic vs NotSoClassic как раз из-за строки

foreach (var pair in row.Selectvalue, i) => new { value, i }

далее код:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace test
{
class Program
{
public static IColumnMax<TRow> ToColumnMax<T, TRow>(IEnumerable<TRow> rows, Func<TRow, T> value, Func<T, T, int> comparer) where T : IComparable
{
return new ColumnMax<T, TRow>(new Column<T, TRow>(value, comparer;
}
public interface IColumnMax<TRow>
{
void Init(TRow row);
void Compare(TRow row);
object Max { get; }
}
public class ColumnMax<T, TRow> : IColumnMax<TRow> where T : IComparable
{
public ColumnMax(Column<T, TRow> column)
{
this.Column = column;
}
public readonly Column<T, TRow> Column;
public T Max;
public void Init(TRow row)
{
Max = Column.Value(row);
}
public void Compare(TRow row)
{
var v = Column.Value(row);
if (Column.Comparer(v, Max) > 0)
Max = v;
}
object IColumnMax<TRow>.Max { get { return Max; } }
}
public class Column<T, TRow>
{
public Column(Func<TRow, T> value, Func<T, T, int> comparer)
{
this.Value = value;
this.Comparer = comparer;
}
public readonly Func<TRow, T> Value;
public readonly Func<T, T, int> Comparer;
}
public class MyClass
{
private int _x;
private string _y;
private double _z;
public int x { get { return this._x; } set { this._x = value; } }
public string y { get { return this._y; } set { this._y = value; } }
public double z { get { return this._z; } set { this._z = value; } }
}

public static IEnumerable<IComparable[]> Data_Arrays;
public static IEnumerable<MyClass> Data_Objects;

public static void DoWork_Classic(IEnumerable<IComparable[]> data)
{
IComparable[] maxes = null;
int i;
foreach (var row in data)
{
if (maxes == null)
maxes = row.ToArray;
else
{
i = 0;
foreach (var value in row)
{
if (value.CompareTo(maxes[i]) > 0)
maxes[i] = value;
i++;
}
}
}
Console.WriteLine(string.Join(", ", (object[])maxes;
}
public static void DoWork_NotSoClassic(IEnumerable<IComparable[]> data)
{
IComparable[] maxes = null;
foreach (var row in data)
{
if (maxes == null)
maxes = row.ToArray;
else
{
foreach (var pair in row.Selectvalue, i) => new { value, i }
{
if (pair.value.CompareTo(maxes[pair.i]) > 0)
maxes[pair.i] = pair.value;
}
}
}
Console.WriteLine(string.Join(", ", (object[])maxes;
}
public static void DoWork_Modern<TRow>(IEnumerable<TRow> rows, Func<TRow, IEnumerable<IComparable>> rowValues)
{
IComparable[] maxes = null;

foreach (var row in rows)
{
if (maxes == null)
maxes = rowValues(row).ToArray;
else
{
foreach (var pair in rowValues(row).Selectvalue, i) => new { value, i }
{
if (pair.value.CompareTo(maxes[pair.i]) > 0)
maxes[pair.i] = pair.value;
}
}
}
Console.WriteLine(string.Join(", ", (object[])maxes;
}
public static void DoWork_Reflection<TRow>(IEnumerable<TRow> rows)
{
var fields = typeof(TRow).GetProperties.Where(field => typeof(IComparable).IsAssignableFrom(field.PropertyType.ToArray;
var p = Expression.Parameter(typeof(TRow;
var d1 = Expression.Lambda<Func<TRow, IComparable>>(Expression.Convert(Expression.Property(p, fields[0] typeof(IComparable p);
DoWork_Modern(rows, row => fields.Select(field => (IComparable)field.GetValue(row, null;
}
public static void DoWork_LambdaReflection<TRow>(IEnumerable<TRow> rows)
{
var fields = typeof(TRow)
.GetProperties
.Where(field => typeof(IComparable).IsAssignableFrom(field.PropertyType
.Select(property =>
{
var p = Expression.Parameter(typeof(TRow;
return Expression.Lambda<Func<TRow, IComparable>>(Expression.Convert(Expression.Property(p, property typeof(IComparable p).Compile;
}
)
.ToArray;
DoWork_Modern(rows, row => fields.Select(field => field(row;
}
public static void DoWork_AntiBoxing<TRow>(IEnumerable<TRow> rows, IColumnMax<TRow>[] maxes)
{

var isInited = false;
foreach (var row in rows)
{
if (isInited)
{
foreach (var column in maxes)
column.Compare(row);
}
else
{
foreach (var column in maxes)
column.Init(row);
isInited = true;
}
}
Console.WriteLine(string.Join(", ", maxes.Select(column => column.Max;
}

static void Method_Classic
{
DoWork_Classic(Data_Arrays);
}
static void Method_NotSoClassic
{
DoWork_NotSoClassic(Data_Arrays);
}
static void Method_DelegateOnArrays
{
DoWork_Modern(Data_Arrays, vals => vals);
}
static void Method_DelegateOnObjects
{
DoWork_Modern(Data_Objects, row => new IComparable[] { row.x, row.y, row.z });
}
static void Method_Reflection
{
DoWork_Reflection(Data_Objects);
}
static void Method_LambdaReflection
{
DoWork_LambdaReflection(Data_Objects);
}
static void Method_AntiBoxing
{
DoWork_AntiBoxing(Data_Objects,
new[]
{
ToColumnMax(Data_Objects, item => item.x, (v1, v2)=> v1.CompareTo(v2
ToColumnMax(Data_Objects, item => item.y, (v1, v2)=> v1.CompareTo(v2
ToColumnMax(Data_Objects, item => item.z, (v1, v2)=> v1.CompareTo(v2
}
);
}

public static void Execute
{
const int rowCount = 1000000;
const int tryCount = 10;

Data_Arrays = Enumerable.Repeat(new IComparable[] { (int)0, "zz", (double)1.1 }, rowCount);
Data_Objects = Enumerable.Repeat(new MyClass { x = 0, y = "zz", z = 1.1 }, rowCount);

var methods = new Action[]
{
Method_Classic, Method_NotSoClassic,
Method_DelegateOnArrays, Method_DelegateOnObjects,
Method_Reflection, Method_LambdaReflection,
Method_AntiBoxing
};

Console.WriteLine("****** WARMING ******");
foreach (var method in methods)
{
Console.Write("{0,-25}:\t", method.Method.Name.Replace("Method_", "";
method;
}
Console.WriteLine("*********************\n\n");

Console.WriteLine("****** TESTING ******");
foreach (var method in methods)
{
var watch = new System.Diagnostics.Stopwatch;
watch.Start;
for (var i = 0; i < tryCount; ++i)
{
method;
Console.CursorTop -= 1;
}
watch.Stop;
Console.WriteLine("{0,-25}:\t{1,7:0.0} ms", method.Method.Name.Replace("Method_", "" watch.Elapsed.TotalMilliseconds);
}
Console.WriteLine("*********************\n\n");
Console.ReadLine;
}
static void Main(string[] args)
{
Execute;
}
}
}

Lord_Max

пока что самый лучший кейс всё равно в 9 раз хуже классического, но спасибо за примеры, с Expression до этого вобще не сталкивался.

Lord_Max

параллельно нашёл http://code.google.com/p/fast-member/source/browse/#hg%2FFa...

Dasar

хочу обратить внимание на Classic vs NotSoClassic:
Спасибо, за цифры! Я знал, что это тормозит, но не подозревал что на столько много на простом коде.
ps
Погонял еще разные варианты:

Ideal_WoEnumerable : 84,2 ms
Ideal : 157,2 ms
Ideal_EnumerableOverArray : 143,2 ms
Ideal_NotIdeal : 707,9 ms
Classic : 254,2 ms
Classic2 : 257,0 ms
Classic_ForeachWoInit : 263,0 ms
NotSoClassic : 1978,3 ms
DelegateOnArrays : 2069,6 ms
DelegateOnObjects : 2463,2 ms
DelegateOnObjects_Classic : 1157,9 ms
DelegateOnObjects_Classic_Yield : 832,0 ms
Reflection : 7807,8 ms
LambdaReflection : 3120,9 ms
AntiBoxing : 1008,7 ms
AntiBoxing_StringAsObject : 420,1 ms

Удивительная вещь! Оказывается, если строки сравнивать как строки, то производительность падает в 5 раз! Скорее всего забыли сравнить ссылки на равенство и сразу переходят на посимвольное сравнение.

public static IColumnMax<TRow> ToColumnMax<T, TRow>(IEnumerable<TRow> rows, Func<TRow, T> value, Func<T, T, int> comparer) where T : IComparable
{
return new ColumnMax<T, TRow>(new Column<T, TRow>(value, comparer;
}
public interface IColumnMax<TRow>
{
void Init(TRow row);
void Compare(TRow row);
object Max { get; }
}
public class ColumnMax<T, TRow> : IColumnMax<TRow> where T : IComparable
{
public ColumnMax(Column<T, TRow> column)
{
this.Column = column;
}
public readonly Column<T, TRow> Column;
public T Max;
public void Init(TRow row)
{
Max = Column.Value(row);
}
public void Compare(TRow row)
{
var v = Column.Value(row);
if (Column.Comparer(v, Max) > 0)
Max = v;
}
object IColumnMax<TRow>.Max { get { return Max; } }
}
public class Column<T, TRow>
{
public Column(Func<TRow, T> value, Func<T, T, int> comparer)
{
this.Value = value;
this.Comparer = comparer;
}
public readonly Func<TRow, T> Value;
public readonly Func<T, T, int> Comparer;
}
public class MyRow
{
public int x { get; set; }
public string y { get; set; }
public double z { get; set; }
}

public static IEnumerable<IComparable[]> Data_Arrays;
public static IEnumerable<MyRow> Data_Objects;
public static MyRow[] Data_Objects_Array;

public static void DoWork_1(IEnumerable<IComparable[]> data)
{
var enumerator = data.GetEnumerator;
if (!enumerator.MoveNext
return;

var maxes = enumerator.Current.ToArray;
while(enumerator.MoveNext
{
var row = enumerator.Current;

var i = 0;
foreach (var value in row)
{
if (value.CompareTo(maxes[i]) > 0)
maxes[i] = value;
i++;
}
}
Console.WriteLine(string.Join(", ", (object[])maxes;
}

public static void DoWork_Ideal_WoEnumerable(MyRow[] data)
{
if (data.Length == 0)
return;

var first = data[0];
var x = first.x;
var y = first.y;
var z = first.z;

for (var i = 1; i < data.Length; ++i)
{
var row = data[i];

if (row.x.CompareTo(x) > 0)
x = row.x;
if (row.y.CompareToobject)y) > 0)
y = row.y;
if (row.z.CompareTo(z) > 0)
z = row.z;
}
Console.WriteLine(string.Join(", ", new object[] { x, y, z };
}
public static void DoWork_Ideal(IEnumerable<MyRow> data)
{
var enumerator = data.GetEnumerator;
if (!enumerator.MoveNext
return;

var first = enumerator.Current;
var x = first.x;
var y = first.y;
var z = first.z;

while (enumerator.MoveNext
{
var row = enumerator.Current;

if (row.x.CompareTo(x) > 0)
x = row.x;
if (row.y.CompareToobject)y) > 0)
y = row.y;
if (row.z.CompareTo(z) > 0)
z = row.z;
}
Console.WriteLine(string.Join(", ", new object[]{x, y, z};
}
public static void DoWork_Ideal_NotIdeal(IEnumerable<MyRow> data)
{
var enumerator = data.GetEnumerator;
if (!enumerator.MoveNext
return;

var first = enumerator.Current;
var x = first.x;
var y = first.y;
var z = first.z;

while (enumerator.MoveNext
{
var row = enumerator.Current;

if (row.x.CompareTo(x) > 0)
x = row.x;
if (row.y.CompareTo(y) > 0)
y = row.y;
if (row.z.CompareTo(z) > 0)
z = row.z;
}
Console.WriteLine(string.Join(", ", new object[] { x, y, z };
}

public static void DoWork_Classic(IEnumerable<IComparable[]> data)
{
IComparable[] maxes = null;
int i;
foreach (var row in data)
{
if (maxes == null)
maxes = row.ToArray;
else
{
i = 0;
foreach (var value in row)
{
if (value.CompareTo(maxes[i]) > 0)
maxes[i] = value;
i++;
}
}
}
Console.WriteLine(string.Join(", ", (object[])maxes;
}
public static void DoWork_Classic2(IEnumerable<IComparable[]> data)
{
IComparable[] maxes = null;
foreach (var row in data)
{
if (maxes == null)
maxes = row.ToArray;
else
{
var i = 0;
foreach (var value in row)
{
if (value.CompareTo(maxes[i]) > 0)
maxes[i] = value;
i++;
}
}
}
Console.WriteLine(string.Join(", ", (object[])maxes;
}
public static void DoWork_NotSoClassic(IEnumerable<IComparable[]> data)
{
IComparable[] maxes = null;
foreach (var row in data)
{
if (maxes == null)
maxes = row.ToArray;
else
{
foreach (var pair in row.Selectvalue, i) => new { value, i }
{
if (pair.value.CompareTo(maxes[pair.i]) > 0)
maxes[pair.i] = pair.value;
}
}
}
Console.WriteLine(string.Join(", ", (object[])maxes;
}
public static void DoWork_Modern<TRow>(IEnumerable<TRow> rows, Func<TRow, IEnumerable<IComparable>> rowValues)
{
IComparable[] maxes = null;

foreach (var row in rows)
{
if (maxes == null)
maxes = rowValues(row).ToArray;
else
{
foreach (var pair in rowValues(row).Selectvalue, i) => new { value, i }
{
if (pair.value.CompareTo(maxes[pair.i]) > 0)
maxes[pair.i] = pair.value;
}
}
}
Console.WriteLine(string.Join(", ", (object[])maxes;
}
public static void DoWork_Classic<TRow>(IEnumerable<TRow> rows, Func<TRow, IEnumerable<IComparable>> rowValues)
{
IComparable[] maxes = null;

foreach (var row in rows)
{
if (maxes == null)
maxes = rowValues(row).ToArray;
else
{
var i = 0;
foreach (var value in rowValues(row
{
if (value.CompareTo(maxes[i]) > 0)
maxes[i] = value;
i++;
}

}
}
Console.WriteLine(string.Join(", ", (object[])maxes;
}
public static void DoWork_Reflection<TRow>(IEnumerable<TRow> rows)
{
var fields = typeof(TRow).GetProperties.Where(field => typeof(IComparable).IsAssignableFrom(field.PropertyType.ToArray;
DoWork_Modern(rows, row => fields.Select(field => (IComparable)field.GetValue(row, null;
}
public static void DoWork_LambdaReflection<TRow>(IEnumerable<TRow> rows)
{
var fields = typeof(TRow)
.GetProperties
.Where(field => typeof(IComparable).IsAssignableFrom(field.PropertyType
.Select(property =>
{
var p = Expression.Parameter(typeof(TRow;
return Expression.Lambda<Func<TRow, IComparable>>(Expression.Convert(Expression.Property(p, property typeof(IComparable p).Compile;
}
)
.ToArray;
DoWork_Modern(rows, row => fields.Select(field => field(row;
}
public static void DoWork_AntiBoxing<TRow>(IEnumerable<TRow> rows, IColumnMax<TRow>[] maxes)
{

var isInited = false;
foreach (var row in rows)
{
if (isInited)
{
foreach (var column in maxes)
column.Compare(row);
}
else
{
foreach (var column in maxes)
column.Init(row);
isInited = true;
}
}
Console.WriteLine(string.Join(", ", maxes.Select(column => column.Max;
}

static void Method_Ideal_WoEnumerable
{
DoWork_Ideal_WoEnumerable(Data_Objects_Array);
}
static void Method_Ideal_EnumerableOverArray
{
DoWork_Ideal(Data_Objects_Array);
}
static void Method_Ideal
{
DoWork_Ideal(Data_Objects);
}
static void Method_Ideal_NotIdeal
{
DoWork_Ideal_NotIdeal(Data_Objects);
}
static void Method_Classic_ForeachWoInit
{
DoWork_1(Data_Arrays);
}
static void Method_Classic
{
DoWork_Classic(Data_Arrays);
}
static void Method_Classic2
{
DoWork_Classic2(Data_Arrays);
}
static void Method_NotSoClassic
{
DoWork_NotSoClassic(Data_Arrays);
}
static void Method_DelegateOnArrays
{
DoWork_Modern(Data_Arrays, vals => vals);
}
static void Method_DelegateOnObjects
{
DoWork_Modern(Data_Objects, row => new IComparable[] { row.x, row.y, row.z });
}
static void Method_DelegateOnObjects_Classic
{
DoWork_Classic(Data_Objects, row => new IComparable[] { row.x, row.y, row.z });
}
static void Method_DelegateOnObjects_Classic_Yield
{
DoWork_Classic(Data_Objects, Yield);
}
static IEnumerable<IComparable> Yield(MyRow row)
{
yield return row.x;
yield return row.y;
yield return row.z;
}
static void Method_Reflection
{
DoWork_Reflection(Data_Objects);
}
static void Method_LambdaReflection
{
DoWork_LambdaReflection(Data_Objects);
}
static void Method_AntiBoxing
{
DoWork_AntiBoxing(Data_Objects,
new[]
{
ToColumnMax(Data_Objects, item => item.x, (v1, v2)=> v1.CompareTo(v2
ToColumnMax(Data_Objects, item => item.y, (v1, v2)=> v1.CompareTo(v2
ToColumnMax(Data_Objects, item => item.z, (v1, v2)=> v1.CompareTo(v2
}
);
}
static void Method_AntiBoxing_StringAsObject
{
DoWork_AntiBoxing(Data_Objects,
new[]
{
ToColumnMax(Data_Objects, item => item.x, (v1, v2)=> v1.CompareTo(v2
ToColumnMax(Data_Objects, item => item.y, (v1, v2)=> v1.CompareToobject)v2
ToColumnMax(Data_Objects, item => item.z, (v1, v2)=> v1.CompareTo(v2
}
);
}

public static void Execute
{
const int rowCount = 1000000;
const int tryCount = 10;

Data_Arrays = Enumerable.Repeat(new IComparable[] { (int)0, "zz", (double)1.1 }, rowCount);
Data_Objects = Enumerable.Repeat(new MyRow { x = 0, y = "zz", z = 1.1 }, rowCount);
Data_Objects_Array = Enumerable.Repeat(new MyRow { x = 0, y = "zz", z = 1.1 }, rowCount).ToArray;

var methods = new Action[]
{
Method_Ideal_WoEnumerable,
Method_Ideal,
Method_Ideal_EnumerableOverArray,
Method_Ideal_NotIdeal,
Method_Classic, Method_Classic2,
Method_Classic_ForeachWoInit,
Method_NotSoClassic,
Method_DelegateOnArrays,
Method_DelegateOnObjects, Method_DelegateOnObjects_Classic, Method_DelegateOnObjects_Classic_Yield,
Method_Reflection, Method_LambdaReflection,
Method_AntiBoxing,
Method_AntiBoxing_StringAsObject,
};

Console.WriteLine("****** WARMING ******");
foreach (var method in methods)
{
Console.Write("{0,-45}:\t", method.Method.Name.Replace("Method_", "";
method;
}
Console.WriteLine("*********************\n\n");

Console.WriteLine("****** TESTING ******");
foreach (var method in methods)
{
var watch = new System.Diagnostics.Stopwatch;
watch.Start;
for (var i = 0; i < tryCount; ++i)
{
method;
Console.CursorTop -= 1;
}
watch.Stop;
Console.WriteLine("{0,-45}:\t{1,7:0.0} ms", method.Method.Name.Replace("Method_", "" watch.Elapsed.TotalMilliseconds);
}
Console.WriteLine("*********************\n\n");
}
}

Dasar


Ideal_WoEnumerable : 80,5 ms
Ideal : 161,2 ms
Ideal_EnumerableOverArray : 143,9 ms
Ideal_NotIdeal : 713,6 ms
Classic : 256,5 ms
Classic2 : 249,0 ms
Classic_ForeachWoInit : 259,2 ms
NotSoClassic : 1980,6 ms
DelegateOnArrays : 2094,6 ms
DelegateOnObjects : 2440,1 ms
DelegateOnObjects_Classic : 1211,9 ms
DelegateOnObjects_Classic_Yield : 828,3 ms
DelegateOnObjects_Classic_Buffered : 680,3 ms *
Reflection : 7973,8 ms
LambdaReflection : 3114,0 ms
AntiBoxing : 1034,1 ms
AntiBoxing_StringAsObject : 440,6 ms


public static void DoWork_Classic_Buffered<TRow>(IEnumerable<TRow> rows, Action<TRow, IComparable[]> rowValues)
{
IComparable[] buffer = new IComparable[100];
IComparable[] maxes = null;
var columnCount = 0;

foreach (var row in rows)
{
if (maxes == null)
{
rowValues(row, buffer);
columnCount = buffer.TakeWhile(v => v != null).Count;
maxes = buffer.Take(columnCount).ToArray;
}
else
{
rowValues(row, buffer);
for(var i = 0; i < columnCount; ++i)
{
if (buffer[i].CompareTo(maxes[i]) > 0)
maxes[i] = buffer[i];
}

}
}
Console.WriteLine(string.Join(", ", (object[])maxes;
}
static void Method_DelegateOnObjects_Classic_Buffered
{
DoWork_Classic_Buffered(Data_Objects, (row, buffer) => { buffer[0] = row.x; buffer[1] = row.y; buffer[2] = row.z; });
}

Dasar

с Expression до этого вобще не сталкивался
Основной подход:
сначала разбираешься какой вариант самый быстрый, а затем с помощью Expression-ов генеришь этот быстрый вариант.

Lord_Max

> Удивительная вещь! Оказывается, если строки сравнивать как строки,
> то производительность падает в 5 раз! Скорее всего забыли сравнить ссылки
> на равенство и сразу переходят на посимвольное сравнение.
вот это не совсем понял:
> Скорее всего забыли сравнить ссылки на равенство
мне казалось они все будут ссылочно не равны, не?

agaaaa

Варианты с итератором без foreach формально не сосем верные: если итератор - IDisposable, то он не будет правильно уничтожен.

Dasar

мне казалось они все будут ссылочно не равны, не?

var s = "zz";
var s1 = "zz";
var s2 = "z" + "z";
var s3 = "zz1".Substring(0, 2);
var s4 = string.Intern(s3);
Console.WriteLine(object.ReferenceEquals(s, s1;
Console.WriteLine(object.ReferenceEquals(s, s2;
Console.WriteLine(object.ReferenceEquals(s, s3;
Console.WriteLine(object.ReferenceEquals(s, s4;


выдаст
True - все одинаковые константы внутри одной сборки компилятор заменяет на одну копию
True - константные выражения вычисляются на этапе компиляции и заменяются на одну копию
False - вызов функции считается неконстантным выражением и вычисляется в runtime-е, давая другую копию строки "zz"
True - Intern обеспечивает, чтобы строки хранились в памяти в одном экземпляре. Это делается только для строк, у которых был явно вызван Intern и для строк с этапа компиляции.

smit1

если итератор - IDisposable, то он не будет правильно уничтожен.
Итератор-то как раз будет, а вот disposable объекты, взятые через него - нет.

Dasar

Итератор-то как раз будет, а вот disposable объекты, взятые через него - нет.
Lost прав - проблема именно в уничтожении iterator. Представим, что в DoWork передали enumerable, который при GetEnumerator открывает подсоединение к базе или создает handle для перечисления файлов. Если этот enumerator не dispose-ить, то тогда соединение с базой или handle останется жить до сборки мусора, что может приводить к неприятным эффектам.
disposable объекты, взятые через него - нет
DoWork не должен dispose-ить эти объекты. Не он их создавал, не ему их и убивать.

Lord_Max

> все одинаковые константы внутри одной сборки компилятор заменяет на одну копию
> константные выражения вычисляются на этапе компиляции и заменяются на одну копию
интересно, не знал, что оно ещё и так оптимизирует, есть повод об этом подумать т.к. в данных есть три строковых столбца, один примерно 200 вариантов, другие 3 и 2, всего данных 2-3 млн строк/день, грузятся пакетами по 2 недели, из текстовых файлов
ушёл смотреть ...

Lord_Max

ps:
> True - Intern обеспечивает, чтобы строки хранились в памяти в одном экземпляре.
> Это делается только для строк, у которых был явно вызван Intern и для строк с этапа компиляции.
на самом деле вот это интересно

smit1

Lost прав
Да, буду внимательно читать код, прежде чем отвечать :crazy:
Оставить комментарий
Имя или ник:
Комментарий: