пятница, 11 сентября 2015 г.

ORM test speed and native method

Я создал проект для проверки скорости работы Entity Framework. Результаты неутешительные.

  • Pure EF 1000 record time 25980
  • Batch EF 1000 record time 17873
  • Off detect EF 1000 record time 17737
  • Off detect batch EF 1000 record time 17620
  • Pure sql 1000 record time 10410
  • Sql SP 1000 record time 10667

Что тут сказать ORM конечно рулят, но когда нужна скорость начинаем тюнить. Исходники тут https://github.com/vkorotenko/OrmTestSpeed

пятница, 14 ноября 2014 г.

Разработка инсталяторов на WIX

Разработка инсталяторов на WIX

Предлагаю разработку инсталяторов на основе технологии http://wixtoolset.org/

Маленькое лирическое отступление. В прошлом все было хорошо и в VisualStudio был тип проекта инсталятора. Сейчас MS решило что все это баловство и по нормальному человек должен купить нормальный продукт для создания установщика.
Не все это хотят, да и внешний вид не очень. Поэтому возникла идея предоставить эту услугу.

Что вы получите  

  • Профессионально выглядящий пакет для вашего приложения
  • Возможность сменить логотип в установщике в соответствии с вашим фирменным стилем
  • При наличии сертификата, подпись пакета 
  • Пакет исходных кодов и инструкция по интеграции в ваш проект
  • Возможность ставить на вашем продукте логотип Microsoft Platform Ready
  • Уменьшение затрат на поддержку приложений

Для кого

  • Независимые разработчики
  • Системные администраторы
  • Разработчики ПО

Возможности

  • MSI - обычный пакет для установки (возможно развертывание через Group Policy)
  • EXE - пакет все в одном, все зависимости в одном файле
  • MSP - пакет обновления
  • Изменение логотипов, надписей, диалогов
  • Логика для установки зависимостей для вашей программы
  • Логика для создания баз данных
  • Логика для внесения записей в реестр
  • Создание иконок на рабочем столе, в папке программы и панели быстрого запуска
  • Выбор компонентов для установки
  • Запуск программы после установки
  • И многое другое...

Контакты

Со мной можно связаться по адресу koroten@ya.ru или скайп vladimir-korotenko

Стоимость

400 р./час. Примерное время изготовления простого установщика  2 часа. Все подробности опишу по почте или скайпу.

воскресенье, 26 мая 2013 г.

Accelerate the entity framework application 20 times

 

Или некоторые соображения по скорости работы EF в реальных приложениях. Довольно часто мы не задумываемся сколько «стоит» удобство использования ORM и всякого «сахара». На днях сел писать программу для импорта данных из sqlite в MS SQL. Структура приведена ниже на рисунках.

Следующие поля в исходной базе строковые:

· Bssid

· Ssid

· Capabilities

· Type

Импортируемые данные

clip_image001

Схема таблиц для вставки данных

clip_image002

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

Дальше использовал EF code first для чтения данных и для записи. Результаты не утешали, на обработку одной записи уходило 170- 210 ms. На 50 000 записях это 142 минуты. Ого!!!!!!! При том что чтение этих данных в редакторе заняло 2 – 3 секунды. Немножко про архитектуру - все в принципе стандартно:

public class KvnSiteContext : DbContext{

public DbSet<Node> Nodes { get; set; }

public DbSet<Feature> Features { get; set; }

public DbSet<UserProfile> UserProfiles { get; set; }

public DbSet<Location> Locations { get; set; }

}

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

sealed internal class Duration{

public Duration()

{

_start = DateTime.Now;

}

private readonly DateTime _start;

public TimeSpan GetDuration()

{

return DateTime.Now - _start;

}

}

Это позволило точно определить виновника по времени.

Итак первоначальный алгоритм:

· Считать записи из таблицы network

· Распарсить фичи

· Каждую фичу вставить в коллекцию и сохранить если новая

· Вставить элемент Node

· Сохранить контекст

· Считать записи из таблицы location

· Найти по полю bssid сущность Node

· Вставить сущность Location

· Сохранить контекст

Результаты были неутешительны, как я уже писал 170 – 210 ms.

Будем что то менять. Переносим сохранение коллекции фич на момент сохранения ноды.

Время уменьшается, до 140-160ms. Дальнейшее расследование показало что слишком много запросов даже при простом поиске, хотя данные уже у нас и зачем искать в БД?

Было применено локальное кэширование всего чего возможно, результаты неутешительные 110-120ms. И тогда я понял что время тяжёлой артиллерии. TSQL!!!!!!!!!!!!

Были созданы 3 хранимые процедуры и одна функция найдена в интернете. В результате время выполнения импорта снизилось с 142 мин до 7. Вот вам один из примеров того как влияют фреймворки на скорость.

Код хранимок:

USE [kvnwifi]

GO

/****** Object: StoredProcedure [dbo].[insertNode] Script Date: 05/26/2013 20:46:50 ******/

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

ALTER PROCEDURE [dbo].[insertNode]

@Id uniqueidentifier OUT,

@bssid nvarchar(MAX),

@ssid nvarchar (MAX),

@frequency int,

@capabilities nvarchar(MAX),

@lasttime bigint,

@lastlat float,

@lastlon float ,

@type int,

@user_id int

AS

BEGIN

-- SET NOCOUNT ON added to prevent extra result sets from

-- interfering with SELECT statements.

SET NOCOUNT ON;

set @id = NEWID();

-- Insert statements for procedure here

INSERT into [dbo].[Nodes]

(

[Id],

[BsSid],

[SSid],

[Frequency],

[LastTime],

[Lat],

[Lon],

[Type],

[User_UserId]

)

VALUES(

@id,

@bssid,

@ssid,

@frequency,

@lasttime,

@lastlat,

@lastlon,

@type,

@user_id

);

exec dbo.InsertCapabilities @Nodeid = @id, @Capabilities = @capabilities

END

USE [kvnwifi]

GO

/****** Object: StoredProcedure [dbo].[InsertCapabilities] Script Date: 05/26/2013 20:47:15 ******/

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

ALTER PROCEDURE [dbo].[InsertCapabilities]

@NodeId uniqueidentifier,

@Capabilities nvarchar(MAX)

AS

BEGIN

SET NOCOUNT ON

DECLARE @i int

DECLARE @numrows int

DECLARE @feature_name nvarchar(100)

DECLARE @numfeature int

DECLARE @fid int

DECLARE @fn_table TABLE(

idx smallint Primary Key IDENTITY(1,1),

[Name] nvarchar(100)

)

INSERT @fn_table

select [Name] from [dbo].[splitstring](@Capabilities)

SET @i = 1

SET @numrows = (SELECT COUNT(*) FROM @fn_table)

IF @numrows > 0

WHILE (@i <= (SELECT MAX(idx) FROM @fn_table))

BEGIN

-- get the next employee primary key

SET @feature_name = (SELECT [Name] FROM @fn_table WHERE idx = @i)

SET @numfeature = (SELECT COUNT(*) from [dbo].[Features] WHERE [FeatureName] = @feature_name)

IF @numfeature != 0

BEGIN

SET @fid = (SELECT [Id] FROM Features WHERE [FeatureName] = @feature_name)

END

ELSE

BEGIN

INSERT INTO dbo.Features ([FeatureName]) VALUES (@feature_name)

SET @fid = @@IDENTITY

END

INSERT INTO dbo.FeatureNodes ([Feature_Id],[Node_Id]) VALUES (@fid,@NodeId)

-- increment counter for next employee

SET @i = @i + 1

END

END

USE [kvnwifi]

GO

/****** Object: StoredProcedure [dbo].[insertLocation] Script Date: 05/26/2013 20:47:33 ******/

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

-- =============================================

-- Author: <Author,,Name>

-- Create date: <Create Date,,>

-- Description: <Description,,>

-- =============================================

ALTER PROCEDURE [dbo].[insertLocation]

@Id uniqueidentifier OUT,

@Bssid nvarchar(MAX),

@Level bigint,

@Lat float,

@Lon float,

@Altitude float,

@Accuracy float,

@LastDate bigint

AS

BEGIN

DECLARE @bsguid uniqueidentifier

DECLARE @return uniqueidentifier

-- SET NOCOUNT ON added to prevent extra result sets from

-- interfering with SELECT statements.

SET NOCOUNT ON;

SET @bsguid = (SELECT [Id] FROM [dbo].[Nodes] WHERE [BsSid] = @Bssid)

SET @Id = NEWID()

INSERT INTO [dbo].[Locations]

(

[Id],

[Bssid],

[Level],

[Lat],

[Lon],

[Altitude],

[Accuracy],

[LastDate]

)

VALUES

(

@Id,

@bsguid,

@Level,

@Lat,

@Lon,

@Altitude,

@Accuracy,

@LastDate

)

END

start: 26.05.2013 18:38:24 end: 26.05.2013 18:45:01

start: 26.05.2013 18:38:24 end: 26.05.2013 18:38:24 item processed: 43301 last time ms: 10,0006

вторник, 7 мая 2013 г.

Automaticaly detect user ticket based on email headers

Введение

Часто мне приходят письма из службы технической поддержки и они меня сильно раздражают такими фразами“Это письмо сгенерировано автоматически, при последующих обращениях указывайте в теме письма номер тикета xxx-1977”. По моему это бред. Сегодня мой коллега столкнулся с этой проблемой, как передать информацию в письме? После работы я посидел покурил RFC И решение пришло

Немного стандартов

http://tools.ietf.org/html/rfc822 собственно это наше все, уникальный заголовок письма описывается в 4.6.1.  MESSAGE-ID / RESENT-MESSAGE-ID это нам и нужно. В вольном переводе:

Данный заголовок должен представлять из себя уникальную строку формируемую сервером при ее отсутствии либо клиентом (Бинго!!!). Смотри дальше  видим заголовки

4.6.2. IN-REPLY-TO
 
4.6.3. REFERENCES

 

Конечно они опциональные, но таки нормальные клиенты их ставят (Бинго!!!)

 

Сам код

этот код стандартный C#, но я думаю на любом другом ЯП можно с легкостью его повторить

using (var client = new SmtpClient())
using(var message = new MailMessage("test@ya.ru",model.To,model.Subject,model.Body))
{
  int requestId = 1977;
  var messageid = string.Format("<{0}_{1}>", Guid.NewGuid().ToString("D"), requestId);
  message.Headers.Add("Message-ID", messageid);
                    client.Send(message);
}

суббота, 27 апреля 2013 г.

Кэширование часто запрашиваемых данных

Класс возник по результату трасировки приложения, оказалось что есть куча дублирующих запросов, трассировка выполнялась с помощью http://miniprofiler.com/, получил целую кучу дублированных запросов. Написал этот кэш менеджер, вместо 42 сек. получил время загрузки 3 секунды.

Примеры вызова:
// время кэширования 3 сек. количество элементов 100, фоновая очистка включена
OneTimeCash cash = new OneTimeCash(3,100,true); 
// время кэширования 400 сек. количество элементов 100, фоновая очистка выключена
var cashe1 = new OneTimeCash(400, 100, false);



using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace OneTimeCach
{
    ///

    ///  Кэширование предмета 1 раз. Используется для тяжелых операций типа обращения к БД
    ///
    /// тип кэшируемого значения
    public class OneTimeCash
    {
        ///

        ///Конструктор класса с инициализацией параметров по умолчанию.
        ///
        /// интервал очистки старых элементов в кэше по умолчанию 5 секунд
        /// Максимальное значение элементов в кэше
        /// Запускать очистку в фоновом потоке по умолчанию false, в этом случае очистка происходит при вставке элемента
        public OneTimeCash(int duration = 5, int maxcount = 100, bool backgroundCleanup = false)
        {
            this.duration = duration;
            this.isBackgroundCleanup = backgroundCleanup;
            this.maxCount = maxcount;
            this.items = new List(maxCount);
            if (isBackgroundCleanup)
            {
                isALive = true;
                Thread thread = new Thread(new ThreadStart(CleanapThreadWorker));
                thread.Start();
            }
        }

        ///

        /// Закрываем поток очистки кэша
        ///
        ~OneTimeCash()
        {
            this.isALive = false;
        }

        ///

        ///  Процедура фоновой очистки кэша
        ///
        void CleanapThreadWorker()
        {
            while (isALive)
            {
                Thread.Sleep(new TimeSpan(0, 0, duration));
                Cleanup();
            }
        }

        ///

        /// Контейнер для кэшируемых данных
        ///
        private class Container
        {
            ///

            /// Времф создания или обновления обьекта в кэше
            ///
            public DateTime Updated;

            ///

            /// Ключ для кэшированного обьекта
            ///
            public string Key;

            ///

            /// Кэшированный обьект
            ///
            public T Value;
        }

        ///

        /// Флаг фонового очищения данных кэша
        ///
        bool isBackgroundCleanup;

        ///

        /// Флаг указывающий на необходимость работы фоновой очистки данных
        ///
        bool isALive;


        public bool IsAlive { get { return isALive; } set { isALive = value; } }
        ///

        /// Промежуток времени жизни обьектов в кэше
        ///
        int duration;

        ///

        /// Максимальное количество обьектов в кэше
        ///
        int maxCount;

        ///

        /// Контейнер для хранения кэшированных обьектов
        ///
        List items;


        ///

        /// Обьект блокировки
        ///
        object locker = new object();

        ///

        /// Помещает элемент в кэш, и производит очистку устаревших элементов если не установлена автоочистка
        ///
        /// Ключ для сохранения кэшированного обьекта
        /// Кэшируемый обьект
        public void PutItem(string key, T cashedObject)
        {
            var item = items.FirstOrDefault(x => x.Key == key);
            if (item != null)
            {
                item.Updated = DateTime.Now;
                item.Value = cashedObject;
            }
            else
            {
                lock (locker)
                {
                    items.Add(new Container() { Key = key, Value = cashedObject, Updated = DateTime.Now });
                }
            }
            if (items.Count > maxCount )
            {
                Cleanup();
            }
        }

        ///

        /// Очистка от устаревших элементов кэша
        ///
        private void Cleanup()
        {
            var result = (from sel in items
                          where sel.Updated > DateTime.Now.AddSeconds(-duration)
                          orderby sel.Updated descending
                          select sel).Take(maxCount);
         
            List container = new List(maxCount);
            container.AddRange(result);
            lock (locker)
            {
                items = container;
            }

        }

        ///

        ///  Проверяет наличие обьекта в кэше
        ///
        /// Ключ для поиска
        ///
        public bool IsItemInCache(string key)
        {
            return items.Any(x => x.Key == key);
        }

        ///

        ///  Получаем закешированный обьект
        ///
        /// Ключ для получения кэшированного обьекта
        /// Возникает когда обьекта нет в кэше
        ///
        public T GetItem(string key)
        {
            var item = items.FirstOrDefault(x => x.Key == key);
            if (item != null)
            {
                return item.Value;
            }
            else
            {
                throw new ArgumentException(string.Format("No cashed object: {0}", key));
            }
        }
    }
}

пятница, 21 декабря 2012 г.

Stellaris LaunchPad приключения на Windows 2008 Server

clip_image001

Декламейры и всяческие отмазки

В статье использованы материалы Хабра пользователя vvzvlad http://habrahabr.ru/post/162737

Интро

Как и вышеупомянутому vvzvlad, мне пришла эта плата. Руки чесались ее «пощупать» давно. Вот она пришла и все началось…

Про доставку

DHL и только они! Представьте 6 дней и эта железка у меня J точнее говоря две, я жадный

ПО и собственно, почему я пишу эту статью

Банально не ставилась CCS. Тут собственно нужно немного описать мою инфраструктуру. В комнате стоит рабочий компьютер, со всеми плюшками. В кладовке стоит сервер, роутеры и все остальное. И глобальная проблема в том, что рабочий компьютер просто не имел достаточно места на HDD для установки CCS. Решил поставить все на сервере, но тут возникла ошибка при установке с сообщением что не может установить из за ошибки распаковки пакета. ХМ, на работе все встало без лишних вопросов. Скорее всего дело в OS, дома у меня 2008 R2, на работе семерка. Режим совместимости не спас, пришлось распаковать утилиту unzip находится вот по этому пути

C:\temp\EK-LM4F120XL-CCS-733\Software\CCS\baserepo\utils.zip utils\bin\unzip.exe

В папку c:\windows дальше все установилось без проблем.

Следующая проблема, это бегание постоянно от рабочего компьютера к серверу, ведь физически плата подключена к USB сервера. Погуглил, оказалось что не у одного меня проблема, господа из 1С тоже страдают (R). Пришлось изобретать велосипед, прокидывать USB через сеть c помощью http://www.usb-over-network.com, как ни странно, но все заработало.

Впечатления

Удобно, чистый код, по сравнению с *уиной, вспомнил что такое память, стек и регистры

Планы

Как всегда наполеоновские:

· Запилить маме розетку управляемую через смс

· Сделать автопилот на основе stellariu’s

четверг, 11 октября 2012 г.

WindowsForm InvokeRequired

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

public static class ControlExtentions
{
/// 
/// Вызов делегата через control.Invoke, если это необходимо.
/// 
/// Элемент управления
/// Делегат с некоторым действием
public static void InvokeIfNeeded(this Control control, Action doit)
{
if (control.InvokeRequired)
control.Invoke(doit);
else
doit();
}
}

и вызов nextLineProgressBar.InvokeIfNeeded(()=>nextLineProgressBar.Minimum = 0);

А смысл этого действа в том что при присвоении значения из другого потока, WinForm очень сильно ругается.

Подсмотрено тут http://sergeyteplyakov.blogspot.com/2009/03/windows-forms-controlinvoke.html

PS можно конечно и старым методом описанным на MSDN, но так по моему красивей  

среда, 3 октября 2012 г.

IOS jQuery POST bug

Проблема: не работает аякс запрос на IOS 5. Читаем jQuery
"Pages fetched with POST are never cached, so the cache and ifModified options injQuery.ajaxSetup() have no effect on these requests."
Мы таки ему не верим и меняем код

$.post("@Url.Action(IfxMVC.News.ActionNames.GetLastNews)",processNewsResult);
На вот этот код
$.ajax({
                type: 'POST',
                url: "@Url.Action(ActionNames.GetLastNews)",
                cache: false
            }).done(processNewsResult);              
         
     

среда, 7 декабря 2011 г.

Undeletable cookie

Зачем? Что бы защититься от накруток рейтингов, смотреть кто был, убивать клоноводов :)
http://samy.pl/evercookie/

воскресенье, 6 ноября 2011 г.

MIcrosoft обучение

«Мы обновили дизайн и наполнение Библиотеки учебных курсов Microsoft, буквально вдохнув новую жизнь в этой ресурс! Теперь преподавателям, студентам и всем энтузиастам стало ещё проще и удобнее находить новые и интересные учебные курсы и методические материалы в одном месте, на MS-library.ru. Почти всё, что нужно для подготовки к лекциям и семинарам, а также для самообразования вы найдёте здесь. Группировка по темам и по продуктам позволит удобно и оперативно находить нужный материал, а в разделе Новые поступления Вы всегда найдёте свежие курсы. Знаете, как сделать сайт ещё более удобным или хотите видеть курсы по определённой теме – напишите нам об этом!»

четверг, 3 ноября 2011 г.

Windows 7 80 port apache denwer

Проблема: не запускается Apache или Denwer на 80 порту.
Решение:
Проблема в специальном драйвере http.sys именно он занимает порт и расшаривает его программам которые знают как это использовать. Решение проблемы: Открыть менеджер устройств\выбрать пункт "показать скрытые устройства"\перейти к устройству http\вызвать его свойства и нажать кнопку остановить. Появиться сообщение о зависимых службах от этого устройства. Записуете их и останавливаете. После этого запускаете нужную вам программу на 80 порту