Sunday, October 26, 2008

Миграция кода из Visual Source Safe в Subversion.

В компании, в которой я на данный момент работаю, для хранения исходного кода используется Subversion (и TFS), но есть старые проекты, которые живут в Microsoft Visual Source Safe 2005. Было p_svnimporterпринято решение перенести старые проекты (код и историю) в Subversion. Первым инструментом, который я нашел, оказался Importer for SVN, написан на java, по описанию умеет экспортировать из: CVS, PVCS, VSS, ClearCase, MKS, StarTeam.

К сожалению, экспорт поддерживается только из VSS 6.0, поэтому сразу успешно импортировать с его помощью мне не удалось. На форуме разработчиков был обнаружен патч, решающий мою проблему. Забрав с сервера последнюю ревизию кода приложения и применив патч, я собрал версию, которая позволила мне импортировать код и историю в Subversion из Microsoft Visual Source Safe 2005. Ура! 

Патч изменяет два класса из пакета org.polarion.svnimporter.vssprovider.internal(VssUtil.java и Vss.java). Для тех, кому понадобится производить подобную миграцию,  я выложил в сеть получившийся билд, убрав лишние провайдеры для других систем контроля версий.

Thursday, October 16, 2008

Object/Object Mapping(отображение объектов на объекты) в .NET

object-to-object mappingИспользуя в своих приложениях доменную модель (Domain Model), как способ организации бизнес логики, в сервисах приложения сталкиваешься с постоянной необходимостью преобразования объектов домена в объекты данных (Data Transfer Object - DTO) для передачи их во внешние системы, различным клиентским приложениям и т.п.  К сожалению, для .NET я смог найти  только следующие библиотеки:

  1. Otis-lib ;
  2. NBearMapping;
  3. Business Logic Toolkit (проект на rsdn);
  4. NPersist;

Со второй не получилось даже толком познакомится, т.к. ни документации, ни примеров я не обнаружил (но как я понял из исходников, она не дотягивает до следующих двух).  Последние две направлены в большей мере на решение O/R M задач, и поддерживают O/O M как подмножество функций необходимых для решения основных задач (объектно-реляционная проекция), поэтому отсутствие необходимых, для решения моей задачи, функций и наличие в них лишних функций (мой O/R M меня устраивает :-)) вынудили меня вычеркнуть их из списка. Таким образом осталось только одна библиотека - Otis.

Сразу скажу, что и она меня не удовлетворила, не смотря на положительное первоначальное впечатление. И так почему оно возникло? Otis специально создан для отображения объектов домена на DTO и, соотвественно, имеет достаточный набор функций для безболезненного осуществления этого процесса. Пример конфигурации преобразования с использованием Otis:

<?xml version="1.0" encoding="utf-8" ?>
<otis-mapping xmlns="urn:otis-mapping-1.0">

<class name="Otis.Sample.Presentation.ArtistInfo, Otis.Sample" source="Otis.Sample.Domain.Artist, Otis.Sample" >
<member name="Id" />
<member name="Description" expression="[$Name + ' [' + $Country + ']' ]" />
<member name="Records" />
<member name="RecordCount" expression="$Records.Count" />
</class>

<class name="Otis.Sample.Presentation.RecordInfo, Otis.Sample" source="Otis.Sample.Domain.Record, Otis.Sample" helper="ConversionHelper" >
<member name="Id" />
<member name="Description" expression="[$Name + ' [' + $YearPublished + ']' ]" />
<member name="SongCount" expression="$Songs.Count" />
<member name="TotalCommentCount" expression="count:$Songs/Comments" />
<member name="AverageSongRating" expression="avg:$Songs/Ratings/Value" />
<member name="AverageSongDuration" expression="avg:$Songs/DurationInSeconds" format="{0:N2} seconds" />
<member name="AlbumDuration" expression="sum:$Songs/DurationInSeconds" />
<member name="Category" expression="$Style" >
<map from="['Pop']" to="Pop"/>
<map from="['Punk']" to="Rock"/>
<map from="['Rock']" to="Rock"/>
<map from="['Classical']" to="Classical"/>
</member>
</class>

</otis-mapping>


Критические недостатки реализации Otis-lib:
  • невозможность использования в серверных приложениях (отсутствует поддержка многопоточности, кэша сгенерированных классов, сборок);


  • использование CodeDom для генерации кода преобразователей;


  • нерасширяемая архитектура;


Ко всем недостаткам реализации добавляются низкие темпы развития проекта. Со всеми этими недостатками Otis-lib становится непригодной в использовании на реальных проектах.

Мои поиски продолжаются, может быть Вы знаете еще о каких-нибудь преобразователях (object to object mapper)?

Friday, October 10, 2008

Самый быстрый способ создания объектов в .NET 3.5

В этой заметке я хочу рассмотреть способы создания объектов для использования их в фабрике объектов и выяснить, какой из способов самый быстрый. Для многочисленных попыток создания будет использован подопытный тип:

public class MyType
{
public MyType(){}

public MyType(string s){}

public MyType(int i, string s){}

public MyType(int i){}

public MyType(object obj, string s){}
}

Вызова натурального конструктора.

var obj= new MyType(10000);

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

Вызов метода  CreateInstance у класса Activator.

var obj = (MyType)Activator.CreateInstance(typeof(MyType), 10000);

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

Вызов метода Invoke у объекта класса ConstructorInfo.

//предварительные затраты, результат должен кэшироваться
ConstructorInfo constructorInfo = typeof(MyType).GetConstructor(new[] { typeof(int) });
//вызов конструктора
var obj = (MyType)constructorInfo.Invoke(new object[] { 10000 });

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

Вызов делегата на динамический метод.

//предварительные затраты, результат должен кэшироваться
var dm = new DynamicMethod("MyCtor", typeof(MyType), new[] { typeof(int) }, typeof(MyType).Module, true);
ILGenerator ilgen = dm.GetILGenerator();
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Newobj, type.GetConstructor(new[] { typeof(int) }));
ilgen.Emit(OpCodes.Ret);
var ctor = (Func<int, MyType>)dm.CreateDelegate(typeof(Func<int, MyType>));
//вызов конструктора
var obj = ctor(10000);

В этом случае с помощью класса DynamicMethod мы динамически создаем статический метод "MyCtor" у нашего класса MyType, единственной задачей которого является создание экземпляра класса MyType через нужный конструктор. Далее мы получаем, делегат на сгенерированный метод и можем его вызывать. Этот метод достаточно быстр: не велики предварительные затраты и время вызова приблизительно в 2-3 раза больше, чем время вызова натурального конструктора, что на много быстрее, в сравнении с ранее рассмотренными способами. Минусом способа является его сложность, немногие разработчики смогут понять этот код.

Вызов динамически скомпилированного лямбда-выражения.

//предварительные затраты, результат должен кэшироваться
var param = Expression.Parameter(typeof(int), "param");
var constructorCall = Expression.New(typeof(MyType).GetConstructor(new[] { typeof(int) }), param);
var constructorLambda = Expression.Lambda(constructorCall, param);
var ctor = (Func<int, MyType>)constructorLambdaTest.Compile();
//вызов конструктора
var obj = ctor(10000);

Основной идеей способа является динамическое создание лямбды вида:

param => new MyType(param)

что вообщем и делает приведенный выше код, и дальнейшее ее использование. Время создания объекта у этого метода совсем на немного отличается от времени создания с помощью натурального конструктора. Таким образом, данный способ - чемпион по времени создания объекта среди способов суррогатного фабрикования. Так же, имеется мнение, что api для создания выражений все же проще чем легкая кодогенерация (Reflection.Emit) из предыдущего способа.  Есть, конечно же,  и ложка дегтя, время, затрачиваемое на подготовку лямбда-выражения в среднем в 9-10 раз больше, чем у предыдущего способа (у которого оно было самым большим из перечисленных).

Какую роль играет время затрачиваемое на подготовку конструктора?

Если в вашем приложении создаются в основном уникальные типы с уникальными конструкторами(т.е. вероятность повторного создания экземпляра класса через уже использованный конструктор низка) время вызова конструктора вырастает ровно на столько, сколько нужно для предварительной генерации конструктора:

// время генерации + время вызова
var ctorForT1 = ObjectFactory.Create<int, TContract>(typeof(T1));
var objOfT1 = ctorForT1(100);
// время генерации + время вызова
var ctor2ForT1 = ObjectFactory.Create<int, string, TContract>(typeof(T1));
objOfT1 = ctor2ForT1(100,"test");
// время генерации + время вызова
var ctorForT2 = ObjectFactory.Create<int, string, TContract>(typeof(T2));
var objOfT2 = ctorForT2(100,"test");

Этот случай далек от реального, но имеет место быть, поэтому в этом случае лучше выбрать 4-ый способ (динамический метод). Если же вероятность повторного создания экземпляра класса через уже использованный конструктор высока(обычно именно так и бывает):

// время генерации + время вызова
var ctorForT1 = ObjectFactory.Create<int, string, TContract>(typeof(T1));
var objOfT1 = ctorForT1(100,"test");

for (int i = 0; i < 10000; i++)
{
// время изъятия конструктора из кэша(пренебрежительно мало и одинаково для всех способов) + время вызова
var obj = ObjectFactory.Create<int, string, TContract>(typeof(T1))(i, "test" + i)
}

то следует выбрать 5-й способ (лямбда-выражение).
Существует еще несколько способов создания объектов в .NET. Я привел не все способы создания объектов в этой статье намеренно, выбрав лишь, по-моему мнению, самые подходящие для использования в фабрике объектов.

Результаты измерений.

Конфигурация:

Intel Core™ Duo Processor T2000 Sequence
2048 Mb of RAM
Windows XP SP3 .NET 3.5 SP1

Время на предварительные затраты(10000 итераций) - Время на создание экземпляра класса(1000000 итераций).

Конструктор с параметрами

Вызова натурального конструктора
00:00:00 - 00:00:00.0242500

Вызов метода CreateInstance у класса Activator
00:00:00 - 00:00:06.5156250

Вызов метода Invoke у объекта класса ConstructorInfo
00:00:00.0156250 - 00:00:02.3593750

Вызов делегата на динамический метод
00:00:00.3281250 - 00:00:00.0615500

Вызов динамически скомпилированного лямбда-выражения
00:00:02.7968750 - 00:00:00.0312500

Конструктор без параметров

Вызова натурального конструктора
00:00:00 - 00:00:00.0222500

Вызов метода CreateInstance у класса Activator
00:00:00 - 00:00:00.2812500

Вызов метода Invoke у объекта класса ConstructorInfo
00:00:00.0076250 - 00:00:01.7812500

Вызов делегата на динамический метод
00:00:00.2968750 - 00:00:00.0625000

Вызов динамически скомпилированного лямбда-выражения
00:00:02.5468750 - 00:00:00.0312500

Код тестового примера:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace ObjectFactoryTest2
{

public class MyType
{
public MyType() { }

public MyType(string s) { }

public MyType(int i, string s) { }

public MyType(int i) { }

public MyType(object obj, string s) { }
}

public class ObjectFactoryTest
{
delegate void Approach(out DateTime prepareStart, out DateTime executeStart, out DateTime finish);
static int LOOP=1;
static int PREPARE_LOOP=1;
static readonly Type type = typeof(MyType);

public static void Main(string[] args)
{
const int TEST_LOOP = 10;
LOOP = 1000000;
PREPARE_LOOP = 10000;

for (int i = 0; i < TEST_LOOP; i++)
{
Console.WriteLine();
Console.WriteLine("--------Ctor with Parameters #" + (i+1) + "--------");
Console.WriteLine();
Console.WriteLine("ParameterAndCtor");
Measure(ParameterAndCtor);
Console.WriteLine();
Console.WriteLine("ParameterAndActivator");
Measure(ParameterAndActivator);
Console.WriteLine();
Console.WriteLine("ParameterAndCtorInfo");
Measure(ParameterAndCtorInfo);
Console.WriteLine();
Console.WriteLine("ParameterAndDynamicMethodAndDelegate");
Measure(ParameterAndDynamicMethod);
Console.WriteLine();
Console.WriteLine("ParameterAndExpressionAndDelegate");
Measure(ParameterAndExpressionAndDelegate);
}

Console.ReadLine();

for (int i = 0; i < TEST_LOOP; i++)
{
Console.WriteLine();
Console.WriteLine("--------Parameterless Ctor #" + (i+1) + "--------");
Console.WriteLine();
Console.WriteLine("ParameterlessAndCtor");
Measure(ParameterlessAndCtor);
Console.WriteLine();
Console.WriteLine("ParameterlessAndActivator");
Measure(ParameterlessAndActivator);
Console.WriteLine();
Console.WriteLine("ParameterlessAndCtorInfo");
Measure(ParameterlessAndCtorInfo);
Console.WriteLine();
Console.WriteLine("ParameterlessAndDynamicMethod");
Measure(ParameterlessAndDynamicMethod);
Console.WriteLine();
Console.WriteLine("ParameterlessAndExpressionAndDelegate");
Measure(ParameterlessAndExpressionAndDelegate);
}

Console.ReadLine();

}

static void Measure(Approach approach)
{
DateTime prepareStart, executeStart, finish;
approach(out prepareStart, out executeStart, out finish);
Console.WriteLine("{0} - {1}", executeStart - prepareStart, finish - executeStart);
GC.Collect(2);
GC.WaitForPendingFinalizers();
}

private static void ParameterAndCtor(out DateTime prepareStart, out DateTime executeStart, out DateTime finish)
{
prepareStart = DateTime.Now;
executeStart = prepareStart;
for (int i = 0; i < LOOP; i++)
new MyType(i);
finish = DateTime.Now;
}

private static void ParameterAndActivator(out DateTime prepareStart, out DateTime executeStart, out DateTime finish)
{
prepareStart = DateTime.Now;
executeStart = prepareStart;
for (int i = 0; i < LOOP; i++)
Activator.CreateInstance(type, i);
finish = DateTime.Now;
}

private static void ParameterAndCtorInfo(out DateTime prepareStart, out DateTime executeStart, out DateTime finish)
{
ConstructorInfo ci = null;
prepareStart = DateTime.Now;
for (int i = 0; i < PREPARE_LOOP; i++)
ci = type.GetConstructor(new[] { typeof(int) });

executeStart = DateTime.Now;
for (int i = 0; i < LOOP; i++)
ci.Invoke(new object[] { i });
finish = DateTime.Now;
}

private static void ParameterAndDynamicMethod(out DateTime prepareStart, out DateTime executeStart, out DateTime finish)
{
Func<int, MyType> dlTest = null;
prepareStart = DateTime.Now;
for (int i = 0; i < PREPARE_LOOP; i++)
{
var dmTest = new DynamicMethod("MyCtor", type, new[] { typeof(int) }, type.Module, true);
ILGenerator ilgenTest = dmTest.GetILGenerator();
ilgenTest.Emit(OpCodes.Ldarg_0);
ilgenTest.Emit(OpCodes.Newobj, type.GetConstructor(new[] { typeof(int) }));
ilgenTest.Emit(OpCodes.Ret);
dlTest = (Func<int, MyType>)dmTest.CreateDelegate(typeof(Func<int, MyType>));
}
executeStart = DateTime.Now;
for (int i = 0; i < LOOP; i++)
dlTest(i);
finish = DateTime.Now;
}

private static void ParameterAndExpressionAndDelegate(out DateTime prepareStart, out DateTime executeStart, out DateTime finish)
{
Func<int, MyType> dlTest = null;
prepareStart = DateTime.Now;
var paramTest = Expression.Parameter(typeof(Int32), "n");
for (int i = 0; i < PREPARE_LOOP; i++)
{
var constructorCallTest = Expression.New(type.GetConstructor(new[] { typeof(int) }), paramTest);
var constructorLambdaTest = Expression.Lambda(constructorCallTest, paramTest);
dlTest = (Func<int, MyType>)constructorLambdaTest.Compile();
}
executeStart = DateTime.Now;
for (int i = 0; i < LOOP; i++)
dlTest(i);
finish = DateTime.Now;
}

private static void ParameterlessAndCtor(out DateTime prepareStart, out DateTime executeStart, out DateTime finish)
{
prepareStart = DateTime.Now;
executeStart = prepareStart;
for (int i = 0; i < LOOP; i++)
new MyType();
finish = DateTime.Now;
}

private static void ParameterlessAndActivator(out DateTime prepareStart, out DateTime executeStart, out DateTime finish)
{
prepareStart = DateTime.Now;
executeStart = prepareStart;
for (int i = 0; i < LOOP; i++)
Activator.CreateInstance(type);
finish = DateTime.Now;
}

private static void ParameterlessAndCtorInfo(out DateTime prepareStart, out DateTime executeStart, out DateTime finish)
{
ConstructorInfo ci = null;
prepareStart = DateTime.Now;
for (int i = 0; i < PREPARE_LOOP; i++)
ci = type.GetConstructor(Type.EmptyTypes);

executeStart = DateTime.Now;
for (int i = 0; i < LOOP; i++)
ci.Invoke(null);
finish = DateTime.Now;
}

private static void ParameterlessAndDynamicMethod(out DateTime prepareStart, out DateTime executeStart, out DateTime finish)
{
Func<MyType> dlTest = null;
prepareStart = DateTime.Now;
for (int i = 0; i < PREPARE_LOOP; i++)
{
var dmTest = new DynamicMethod("MyCtor", type, Type.EmptyTypes, type.Module, true);
ILGenerator ilgenTest = dmTest.GetILGenerator();
ilgenTest.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
ilgenTest.Emit(OpCodes.Ret);
dlTest = (Func<MyType>)dmTest.CreateDelegate(typeof(Func<MyType>));
}
executeStart = DateTime.Now;
for (int i = 0; i < LOOP; i++)
dlTest();
finish = DateTime.Now;
}

private static void ParameterlessAndExpressionAndDelegate(out DateTime prepareStart, out DateTime executeStart, out DateTime finish)
{
Func<MyType> dlTest = null;
prepareStart = DateTime.Now;
for (int i = 0; i < PREPARE_LOOP; i++)
{
var constructorCallTest = Expression.New(type.GetConstructor(Type.EmptyTypes));
var constructorLambdaTest = Expression.Lambda(constructorCallTest);
dlTest = (Func<MyType>)constructorLambdaTest.Compile();
}
executeStart = DateTime.Now;
for (int i = 0; i < LOOP; i++)
dlTest();
finish = DateTime.Now;
}

}
}