Вот решил перейти с С++ на С# и наткнулся на такую проблему. Как я понял, в шарпе нет шаблонов как таковых, и это компенсируется интерфейсами и обобщенными классами. Вопрос: есть ли способ, дать понять компилятору, что Type - числа? , чтобы использовать операторы стандартных типов, иначе не получается реализовать сложение и тп. матриц. Или есть другое решение? Вот что сделал:
public class Matrix> MatrixValue;
public Matrix(int rows, int cols)
{
MatrixValue = new List>(rows);
foreach (var item in MatrixValue)
{
item.Capacity = cols;
}
}
public Matrix()
{
MatrixValue = new List
>();
}
public static Matrix
Ответ
Самый эффективный на данный момент метод — подключить из nuget System.Numerics.Vectors, и векторизовать ваши операции. Заодно вы получите их в generic-виде. Для System.Numerics.Vectors.Vector
Вот вам упрощённый класс, представляющий вектор произвольной длины с операцией сложения:
class VectorNum
public VectorNum(int size)
{
var vectorSize = System.Numerics.Vector
public void AddTo(VectorNum
Бенчмарки внизу.
Более простой (но менее эффективный) метод, вероятно, такой:
static T add(T l, T r)
{
return (T)((dynamic)l + r);
}
На текущий момент нельзя объяснить компилятору, что тип генерик-параметра — числовой. Хуже того, это проблематично, поскольку для каждой инстанциации генерик-типа работает один и тот же IL. А сложение int'ов и double'ов представлено на IL-уровне разными инструкциями.
Измерение эффективности.
Вот такая тестовая программа:
static class Program
{
static void Main()
{
VectorExpr
// прогрев
ve1.Init(1.1, 2.2); ve2.Init(1.1, 2.2);
vd1.Init(1.1, 2.2); vd2.Init(1.1, 2.2);
vn1.Init(1.1, 2.2); vn2.Init(1.1, 2.2);
vv1.Init(1.1, 2.2); vv2.Init(1.1, 2.2);
ve1.AddTo(ve2); vd1.AddTo(vd2); vn1.AddTo(vn2); vv1.AddTo(vv2);
Stopwatch swE = new Stopwatch(), swD = new Stopwatch(),
swN = new Stopwatch(), swV = new Stopwatch();
swE.Start();
for (int i = 0; i < 1000 * 1000; i++)
ve1.AddTo(ve2);
swE.Stop();
var resultE = ve1.Sum();
swD.Start();
for (int i = 0; i < 1000 * 1000; i++)
vd1.AddTo(vd2);
swD.Stop();
var resultD = vd1.Sum();
swN.Start();
for (int i = 0; i < 1000 * 1000; i++)
vn1.AddTo(vn2);
swN.Stop();
var resultN = vn1.Sum();
swV.Start();
for (int i = 0; i < 1000 * 1000; i++)
vv1.AddTo(vv2);
swV.Stop();
var resultV = vv1.Sum();
Console.WriteLine($"Expression: elapsed time {swE.Elapsed}, result = {resultE}");
Console.WriteLine($"Dynamic : elapsed time {swD.Elapsed}, result = {resultD}");
Console.WriteLine($"Native : elapsed time {swN.Elapsed}, result = {resultN}");
Console.WriteLine($"Vector : elapsed time {swV.Elapsed}, result = {resultV}");
}
}
class VectorExpr
static VectorExpr()
{
var a = Expression.Parameter(typeof(T));
var b = Expression.Parameter(typeof(T));
add = Expression.Lambda
T[] payload;
public VectorExpr(int size)
{
payload = new T[size];
}
public void Init(T seed1, T seed2)
{
T curr = seed1;
for (int i = 0; i < payload.Length; i++)
{
payload[i] = curr;
curr = add(curr, seed2);
}
}
public void AddTo(VectorExpr
public T Sum() => Enumerable.Aggregate(payload, add);
}
class VectorDyn
T[] payload;
public VectorDyn(int size)
{
payload = new T[size];
}
public void Init(T seed1, T seed2)
{
T curr = seed1;
for (int i = 0; i < payload.Length; i++)
{
payload[i] = curr;
curr = add(curr, seed2);
}
}
public void AddTo(VectorDyn
public T Sum() => Enumerable.Aggregate(payload, add);
}
class VectorNum
public VectorNum(int size)
{
var vectorSize = System.Numerics.Vector
public void Init(T seed1, T seed2)
{
var values = new T[System.Numerics.Vector
public void AddTo(VectorNum
public T Sum()
{
var vectorSum = Enumerable.Aggregate(payload, (v1, v2) => v1 + v2);
return Enumerable.Range(0, System.Numerics.Vector
class VectorNatDouble
{
double[] payload;
public VectorNatDouble(int size)
{
payload = new double[size];
}
public void Init(double seed1, double seed2)
{
double curr = seed1;
for (int i = 0; i < payload.Length; i++)
{
payload[i] = curr;
curr = curr + seed2;
}
}
public void AddTo(VectorNatDouble r)
{
double[] ldata = payload, rdata = r.payload;
if (ldata.Length != rdata.Length)
throw new ArgumentException();
for (int i = 0; i < ldata.Length; i++)
ldata[i] = ldata[i] + rdata[i];
}
public double Sum() => payload.Sum();
}
сравнивает подход с dynamic, подход с Expression, подход с векторизацией и нативный необобщённой код.
Результаты (Release mode, вне Visual Studio, x64 на 64-битной Windows) по пяти запускам:
...\bin\x64\Release>Test.exe
Expression: elapsed time 00:00:03.2824695, result = 4613743627468,84
Dynamic : elapsed time 00:00:22.1788664, result = 4613743627468,84
Native : elapsed time 00:00:01.3229095, result = 4613743627468,84
Vector : elapsed time 00:00:01.0145604, result = 4613743627468,84
...\bin\x64\Release>Test.exe
Expression: elapsed time 00:00:03.3579191, result = 4613743627468,84
Dynamic : elapsed time 00:00:22.6582909, result = 4613743627468,84
Native : elapsed time 00:00:01.3391487, result = 4613743627468,84
Vector : elapsed time 00:00:01.0315590, result = 4613743627468,84
...\bin\x64\Release>Test.exe
Expression: elapsed time 00:00:03.3705103, result = 4613743627468,84
Dynamic : elapsed time 00:00:21.9322240, result = 4613743627468,84
Native : elapsed time 00:00:01.3294231, result = 4613743627468,84
Vector : elapsed time 00:00:01.0168434, result = 4613743627468,84
...\bin\x64\Release>Test.exe
Expression: elapsed time 00:00:03.3426822, result = 4613743627468,84
Dynamic : elapsed time 00:00:22.5870899, result = 4613743627468,84
Native : elapsed time 00:00:01.3227761, result = 4613743627468,84
Vector : elapsed time 00:00:01.0202565, result = 4613743627468,84
...\bin\x64\Release>Test.exe
Expression: elapsed time 00:00:03.3142676, result = 4613743627468,84
Dynamic : elapsed time 00:00:21.8089834, result = 4613743627468,84
Native : elapsed time 00:00:01.2854937, result = 4613743627468,84
Vector : elapsed time 00:00:00.9963579, result = 4613743627468,84
Среднее для Expression 3.3336 секунд, для dynamic 22.2331 секунд, нативный код 1,3200 секунд, и векторизованный код 1.0159 секунд. Так что векторизованный вариант быстрее даже нативного на 30%.
Результаты для int ещё лучше:
...\bin\x64\Release>Test.exe
Expression: elapsed time 00:00:02.9075671, result = -1870659584
Dynamic : elapsed time 00:00:22.9417084, result = -1870659584
Native : elapsed time 00:00:01.5297815, result = -1870659584
Vector : elapsed time 00:00:00.5281395, result = -1870659584
Векторизация даёт ускорение втрое по сравнению с нативным кодом.
Комментариев нет:
Отправить комментарий