February 18, 2010

Управляемая память тоже утекает

Предыстория

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

Код написан на C#: .net, сборка мусора, все дела... Никто не ожидал подвоха. И вот в один прекрасный момент в виде данных попробовали передавать кучу относительно небольших, размером ~200Кб, картинок. И тут всё сломалось... Куда-то стала пропадать свободная память. Открыли Process Explorer, посмотрели, удивились: растёт LOH. Название говорящее, как бы намекает нам о том, что мы были не правы, когда понадеялись на сулящий золотые горы механизм сборки мусора.

LOH

LOH - Large Object Heap, что в переводе на русский звучит как "боЛьших Объектов Хранилище", является тем самым участком памяти, в который попадают, как можно догадаться из названия, большие объекты. Достаточно большим объектом в .Net считается всё, что больше 85000 байт. К таким объектам не применимы общие правила работы с памятью, то есть они не проходят через поколения сборщика мусора, а при инициализации попадают прямо в ЛОХ, потому как полагается, что объекты такого размера с большой вероятностью могут ещё пригодиться, а проводить их через все стандартные процедуры - слишком дорогое занятие.

Что же случилось

Согласно всем рекомендациям ведущих дотнетоводов (и дотнетоводоведов) для операций со строками использовался класс StringBuilder, куда все пакеты добавлялись при помощи метода Append. Для проверки данных по мере их накопления время от времени вызывался StringBuilder.ToString, который, при передаче картинок создавал гигантского размера строку, которая в свою очередь помещалась в LOH, и никуда оттуда не исчезала, даже несмотря на отсутствие внешних ссылок. Согласно найденной документации, сборка мусора в Generation 2 и LOH происходит одновременно и только по одной из следующих причин: 1) размер занимаемой поколением памяти достиг определённого предела, 2) явный вызов GC.Collect, 3) системе категорически не хватает памяти. По всей видимости, ни одно из этих условий не было выполнено, поэтому картинки продолжали выедать системную память.

Как такое лечить

Во-первых, чтобы понять что конкретно происходит с памятью в .net приложении, можно использовать вышеназванный ProcessExplorer, который позволяет в свойствах процесса, на вкладке .NET выбрать набор счётчиков производительности ".NET CLR Memory" и отслеживать их значения в процессе работы приложения. Кроме того, можно воспользоваться стандартным интерфейсом просмотра счётчиков производительности - утилитой perfmon, которая, насколько мне известно, присутствует в любой современной версии Windows.

Во-вторых, чтобы однозначно определить, что же является тем ресурсом, который выедает всю память, можно воспользоваться замечательным набором Debugging Tools For Windows, который можно задаром получить с сайта Microsoft. Супер подробное описание способа работы с этим дебагером можно найти в блоге Tess Ferrandez. Здесь же я перечислю пару команд, которые помогли мне:
Этих 5 простых команд вполне достаточно, чтобы обнаружить виновников утечек памяти, а после их устранения проверить, что теперь-то всё работает как надо.

В нашем случае пришлось отказаться от использования StringBuilder в пользу List<string> и модифицировать процедуру проверки данных для работы над списком пакетов, а не всем набором в виде одной строки. В результате получилось избавиться от гиганских строк, а более мелкие строки при очистке списка не переживают очередной сборки мусора.

Заключение

Строки, как и StringBuilder не имеют механизма принудительной очистки занимаемой памяти, так что в следующий раз, когда понадобится разрабатывать что-то подобное, я скорее всего задумаюсь об использовании MemoryStream, или ещё чего подобного.

February 9, 2010

Прелюдия к компилятору

По причине сложности внедрения кучи блоков кода в пост, самое свежее своё творение решил оформить в виде wiki и сложить его прямо рядом с репозиторием кода на bitbucket. Так что, если кому интересно почитать про то, как можно конвертировать строковые where-выражения в .Net Expression Trees, и всё это на F# - языке, которого нет - добро пожаловать по адресу http://bitbucket.org/moiseev/wepr/wiki/Intro.

July 16, 2009

Немного об инициализации коллекций

Читаю сейчас Foundations of F# (тут можно почитать). И вот в главе про императивное программирование наткнулся на интересный способ инициализации коллекций.

Часто бывает так, что надо создать список/словарь/whatever и тут же наполнить его какими-нибудь элементами.

let myList = new System.Collections.Generic.List<int>()
myList.Add(1)
myList.Add(2)
myList.Add(3)

Да, я знаю что можно написать короче, например:

let myList = new System.Collections.Generic.List<int>()
[1;2] |> Seq.iter myList.Add

Но не об этом сейчас разговор. Проблема в том, что такой код мозолит глаза инициализацией за несколько операций. Не могу сказать, такая ли уж это проблема, но чувствую, что что-то не так. ;-)

И вот какое решение предлагается:

let myList =
let temp = new System.Collections.Generic.List<int>()
temp.Add(1)
temp.Add(2)
temp.Add(3)
temp

То есть, вроде бы и функция, но вычисляется один раз, то есть по сути значение. Очень, кстати, похоже на инициализаторы коллекций в свежем C#.

var myList = new System.Collections.Generic.List<int>() { 1, 2, 3 };

Красота?


January 27, 2009

История одного побочного эффекта

Столкнулся сегодня с одим преинтереснейшим побочным эффектом безобидного, казалось бы, действия. Говорили мне все тексты про Haskell, что сайд-эффекты это плохо, и не потому плохо, что они есть, а потому, что снаружи не видать.

Какое-то время назад я размышлял тут о проблеме перехвата консольного вывода от процессов. Чтобы всё заработало тогда я установил переменную окружения PYTHONUNBUFFERED в 1 и остался доволен полученным результатом. Некоторое время после этого Python не трогал, да и забыл вообще. И вот сегодня откликнулось!

Запускаю “manage.py syncdb” которое Django генерирует, так как запускаю на пустой базе, предлагают мне пользователя создать административного. И вопрос задают “yes” или “no”. По привычке отвечаю yes <Enter> – ничего. Снова тот же вопрос. Ладно, благо код открытый, чем разбираться в проблеме, проще её исправить на месте. Иду в исходники, убираю проверку условия. (Тут был небольшой казус с Вистой, но это совсем другая история.) Возвращаюсь к шагу №1 “manage.py syncdb”. Отлично! Никаких вопросов – просто “Введите имя пользователя”. Ввожу admin <Enter>. На этот раз invalid user name. Вроде как какие-то непечатные символы в имени пользователя. Но я то знаю, что их там нет!

Запускаем любимый редактор, пишем там мало-мало кода, дабы проверить догадку. Так и есть! Возврат каретки дописывается в конец строки. Надо сказать, что с момента начала всей эпопеи прошло уже порядочно времени, и поиск проблемы начинает утомлять. Дай, думаю, запущу interactive console. Сказано-сделано, запускаю, пишу 2+2 <Enter> и получаю, что бы вы думали?! SyntaxError! Шик блеск!

И главное, что все мои запросы в Google и Яндекс а-ля “strange behavior of pyhon on vista” вели вникуда. В конце концов с горя полез искать настройки символа перевода строки по умолчанию. Конечно, ничего такого не нашёл, потому как настройка шибко системная, но зато выполнил разок команду SET, которая и показала мне, что PYTHONUNBUFFERED стоит себе в уголочке скромной единичкой и мешает честному человеку дело делать.

Пожалуй, такой опыт сильнее любых многопроцессоров привьёт любовь к чистым функциям и стойкое отвращение к побочным эффектам.


December 25, 2008

Операции на открытом сердце

Страшно звучит, правда? Хочу поразмышлять немного на тему такого рода операций в приложении к программным продуктам.

Есть классические способы понять чем занимается приложение в конкретный момент времени: отладка и логи. Лично я предпочитаю логи, потому что они более приближены к боевым условиям. Любой дебагер, каким бы хорошим он ни был, всё равно вносит какие-то коррективы в работу приложения, а иногда отладить конкретную функциональность под дебагером вообще невозможно, вспомним хотя бы треды. Добавив немного логирующего кода, можно после тестового прогона приложения получить сколько угодно реальной информации, сообщающей в какой последовательности исполнялся тот или иной код, чему были равны переменные и так далее. Важно, что приложение исполняется как есть, без оверхеда, присущего исполнению в отладчике. К сожалению, всё не так прекрасно, потому что добавление логирующего кода - это, как ни странно звучит, изменение кода, а значит, требует пере-сборки системы. В "полевых" условиях такой финт невозможен.

Представьте ситуацию. Приходим к пользователю, он говорит "не работает". Смотрим, действительно, в определённой ситуации не работает как надо, не совсем понятно почему, но примерная область нахождения источника проблем известна. Исходного кода под рукой нет. В простейшем случае говорим "окэй, я посмотрю" и удаляемся чинить или полагаемся на авось: "а вдруг оно ему и не надо и он больше никогда этим пользоваться не будет".

А что если взять и встроить в приложение некий DSL, который позволил бы "покопаться" во внутреннем состоянии работающего приложения?! Я, как обычно, имею ввиду Microsoft .Net Framework. В качестве DSL сейчас запросто может выступать IronPython, хотя, когда DLR  увидит свет, таким DSL может стать любой .net язык программирования. Просто вставляем engine куда-нибудь внутрь приложения, по нажатию секретного shortcut'а запускаем консоль, куда передаём в качестве контекста внутреннее состояние приложения и вуа ля! у нас есть доступ к моментальному слепку состояния системы, возможность его мониторить и даже в какой-то мере менять, выполнять действия за систему, контролировать ход исполнения, добавлять необходимое логирование. Правда, нельзя эти изменения потом сохранить, ну или я просто пока не придумал как это можно было бы сделать :-)

Идея, конечно, не нова. На просторах интернета можно найти кучу статей и постов на тему "как добавить поддержку скриптов ХХХ  в приложение, написанное на YYY", но лично я не разу не встречал ни в теории ни на практике возможности доступа к внутренностям приложения на лету.

Any ideas?

October 21, 2008

Очень строгая типизация

Волею судеб я теперь играюсь с F#. (Сначала вроде бы увлёкся Haskell’ом, но потом понял, что F# мне как-то ближе, потому как он весь насквозь .net-ный). Если кто не знает, то язык этот – первый и пока что, кажется, единственный функциональный язык для платформы .net. Любители тонкостей скажут, что не такой уж он и функциональный, скорее мультипарадигменный (ох и сложное слово, надо его спеллчекером…), и будут правы. Так и есть, но я лично рассматриваю его как площадку для игр с функциональным программированием. И вот до чего я доигрался.

Есть у меня один микропроект. Даже не проект, а так, игрушка, которую я решил реализовать на F#, чтобы и утилитку полезную получить и с новым языком разобраться. Писал я писал, разбирался-разбирался и в один прекрасный момент понадобилось мне создать web запрос, да не простой, а с кукисами. Как бы я поступил в C#:

HttpWebRequest req =
(HttpWebRequest)WebRequest.Create("http://google.com");
req.CookieContainer.Add(new Cookie("my_cookie", "hello"));

Самая главная часть этого небольшого куска кода во второй строке, где создаётся WebRequest. Видите, как занимательно его приходится создавать? При помощи WebRequest.Create, который уже сам определяет какой конкретный тип запроса надо создать. В данном случае он создаст HttpWebRequest, поэтому преобразование типа здесь отлично сработает и даст нам доступ ко всем методам и свойствам, которые присущи http запросу.

В чём сложность с точки зрения F#? Как оказалось, в его строгой типизации.

let req = WebRequest.Create("http://google.com")
(* will cause an error *)
let httpreq = req :> HttpWebRequest

Архитектура библиотеки классов, как оказалось, не предполагает возможности создания экземпляров HttpWebRequest самих по себе, всё происходит исключительно через WebRequest.Create, а приведение типа объекта от более общего к более частному, получается, невозможно, а CookieContainer есть только в конкретном типе. Я пытался использовать обходные пути, типа Convert.ChangeType, но он, оказывается, возвращает вообще Object, и в итоге необходимо ещё более невозможное приведение типа :-)

Вот такая вот слишком строгая типизация. Если у кого есть мысли как это обойти – welcome!


June 5, 2008

О перенаправлении вывода

Я почти уверен, что каждый программист хотя бы раз в жизни, но сталкивается с проблемой перенаправления потока вывода процесса куда-то наружу. Например, при написании GUI обёртки для консольного приложения, которое "кто-то написал давным давно, да к тому же под *nix, а потом добрые люди спортировали это чудо в Windows и так и оставили". Я долго избегал этой участи, может быть подсазнательно, но вот и моё время пришло.

Напомню, что дело имею в основном с C# и .Net, так что разговаривать будем с использованием этих слов. Хотя, наверное, теория, она одна, и на чём ты её реализовываешь - дело десятое.

Начну, пожалуй, издалека. Если обратить внимание на правый side-bar данного блога можно запросто заметить иконку замечательного текстового редактора Notepad++, которым я иногда пользуюсь, когда это удобнее Vim'а. Так вот, некоторое время назад я столкнулся с ним вплотную с точки зрения использования его в качестве некого подобия IDE. И существенной частью этого использования должна была стать возможность запускать некие скрипты, набираемые в поле редактирования, прямо из редактора, используя внешний интерпретатор. Сейчас это реализовано через plug-in, кому интересно, могут посмотреть в документацию и узнать подробности.

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

Позволю себе углубиться ещё дальше в историю вопроса. Многим известно, что перенаправление потока вывода консольного приложения можно осуществить, так сказать, голыми руками, просто набрав в консоли "program.exe > out". Известно это было и мне, более того, я всегда активно пользовался этой возможностью, за что однажды в институте получил упрёк от преподавателя за "недружелюбность интерфейса". Вобщем, знание о магическом операторе ">" было мне ведомо и хранилось до поры до времени внутри головного мозга.

Ну что ж. Задача понятна. Будем искать решение. Куда идём? Правильно! Для начала MSDN Library.

ProcessStartInfo info = new ProcessStartInfo(executable_path, command_line_parameters);
info.UseShellExecute = false; // это важно
info.RedirectStandardOutput = true; // и это тоже не менее важно
Process proc = Process.Start(info);
string output = proc.StandardOutput.ReadToEnd();
proc.WaitForExit();

Поход ясен как день, но проблемы не решает. Хотелось бы чего-то более интерактивного. "ReadToEnd", конечно, гарантирует, что весь вывод, который когда-то предполагался для консоли, попадёт в мои руки через "output", но произойдёт это только после того, как процесс завершится, а в моём случае произойти это может сколь угодно нескоро. А значит, пользователь, который имел неосторожность нажать кнопку "Старт" в моём приложении, будет недоумевать, почему всё встало и ничто не булькает и не моргает, чтобы хоть каким-то способом заявить о кипящей где-то глубоко внутри работе.

Стоит отметить, что в МСДНе есть ещё много иностранных слов о том, что чтение перенаправленного потока вывода можно осуществлять как синхронно, так и не очень. Но все мои потуги вызвать "proc.StandardOutput.BeginReadToEnd()" или даже "proc.BeginOutputReadLine()" привели к тому же печальному результату, даже более того, в последнем случае по завершении процесса я получал только первую строку его вывода.

Собственно, настало время перестать слушать маму читать что написали другие, и написать что-то самому. Вот здесь как раз вспомнился опыт общения с Notepad++, где одна из переменных для настройки называлась как-то вроде "OutputPingTimeout", или по-другому но с тем же смыслом.

Идея в том, чтобы периодически тыкать поток вывода дочернего процесса на момент наличия в нём "чего почитать". Ну и конечно, эти тычки должны происходить с некоторой периодичностью в процессе работы процесса. (Да, я знаю, что масло масляное.) В этом нам помогут старые добрые треды (будем называть их так, чтобы не путать с потоками ввода/вывода). Дабы не городить огород из "public static void Main", возьмём в руки IronPython и забацаем, так сказать, по-быстрому.

import clr
from System.Diagnostics import ProcessStartInfo, Process
from System.IO import StreamReader
from System.Threading import Thread, ThreadStart
from System import Exception

def create_start_info():
   info = ProcessStartInfo(r"...")
   info.Arguments = "..."
   info.UseShellExecute = False
   info.RedirectStandardOutput = True
   return info

def run_proc():
   proc = Process.Start(create_start_info())
   return proc

reader = None

def thread_proc():
   while True:
      s = reader.ReadLine()
      if not s:
         break
      print 'read from reader:', s

if __name__ == "__main__":  
   try:
      try:
         proc = run_proc()
         reader = proc.StandardOutput
         Thread.CurrentThread.Join(100)
         thread = Thread(ThreadStart(thread_proc))
         thread.Start()
         proc.WaitForExit()
         print 'Process finished'
         thread.Join()
         print 'Thread finished'
      except Exception, ex:
         print ex.ToString()
   finally:
      proc.Close()

Ну вот как-то так. И что самое удивительное - этот код заработал! Вроде бы всё должно быть понятно.

Один только момент, который хотелось бы уточнить, это окончание треда. Тред крутится до тех пор, пока "reader.ReadLine" не вернёт null (или None в случае с Python), а это должно произойти только когда поток закончится, а значит, ккогда закочнится выполнение дочернего процесса. Следовательно, если исходить из предположения, что процесс рано или поздно закончит своё существование, можно надеяться, что и "thread.Join()" без таймаута не приведёт к "мёртвому висяку", известному так же под именем "dead lock".

Вот на этой оптимистичной ноте, позвольте закончить и откланяться.


July 25, 2007

Being lazy

Много всякого говорят про ленивые вычисления, то есть, когда какой-то код выполняется только в том случае, когда его результат действительно нужен, а не "впрок". Не буду сильно углубляться в описание того, что же это такое, ленивые вычисления, про это много уже написано. Последним, что я встретил на эту тему, был рассказ об itertools в Python.

Отличная, надо сказать, штука, эти "вычисления по запросу". В приведённой выше статье про itertools много хорошего говорится об их пользе в качестве средства обработки коллекций. И правда! Замечательная идея, взять коллекцию и пробегать её только тогда, когда это необходимо. Или не пробегать, а генерировать следующий элемент, как в классическом примере про бесконечную последовательность Чисел Фибоначчи, которая упоминается почти везде, где разговор идёт о правильности ленивых вычислений. Особенно любят это дело (бесконечные последовательности и ленивые вычисления) сторонники Haskell. Нигде я не встречал так часто слова "lazy" как в литературе по этому замечательнм языку.

Сам я, к сожалению, пока что не познал премудростей Haskell'а, и моим рабочим языком программирования по прежнему является старый добрый C#. В своей 2.0 версии C# получил от разработчиков замечатльную возможность, называемую итераторами. То есть, нет, итераторы там были и раньше, но был добавлен новый способ их реализации. Если раньше для того, чтобы вернуть коллекцию (лучше, наверное, сказать "последовательность"), надо было писать свою собственную реализацию IEnumerator, то теперь достаточно выучить волшебное слово "yield" (кстати, знакомое всем Python и Ruby разработчикам, но действующее, как обычно, несколько по-своему), а точнее фразу "yield return", которая совершенно незаметно сделает всю грязную работу. Есть, правда, некотороые ограничения на код с использванием yield return, но это не столь важно в контексте нашего разговора.

Итак. В C# 2.0 возможна реализация итераторов, которые позволяют пробегать коллекцию "ленивым" способом, то есть только по надобности. Ну и, конечно, если есть инструмент типа "молоток", то, в процессе забивания гвоздей, можно смело ожидать удара по любимой руке. Так и с итераторами. Для наглядности приведу код.

using System;
using System.Collections.Generic;

namespace Foo {
public static class Bar{

public static IEnumerable<int> Map(IEnumerable<int> items){
foreach(int i in items){
Console.WriteLine(string.Format("inside Map: {0}", i));
yield return i;
}
}

public static void JustDoIt(IEnumerable<int> ints){
foreach(int i in ints){
Console.WriteLine(string.Format("inside JustDoIt: {0}", i));
}
}

public static void Main(string[] args){
List<int> ints = new List<int>();
ints.AddRange(new int[]{1, 2, 3});
IEnumerable<int> afterMap = Map(ints);
try{
ints.Add(123);
ints.RemoveAt(1);
JustDoIt(ints);
}
catch(Exception ex){
Console.WriteLine(ex.ToString());
}
}
}

Простенький итератор представлен методом Map, который не делает ровным счётом ничего, кроме того, что "лениво" пробегает исходную коллекцию, выводя по дороге элементы коллекции на печать. Простой метод JustDoIt имитирует метод-потребитель итератора. Ну и собственно, метод Main. В нём создаётся коллекция [1, 2, 3], после этого создаётся тот самый итератор. Затем коллекция изменяется, и лишь вслед за этим вызывается метод-потребитель.


Поведение ожидаемое (если, конечно, понимать суть). В JustDoIt была обработана не та коллекция, которая передавалась в Map, а её изменённая версия, хотя сам Map был вызван до модификации, и вроде бы ints и afterMap ссылаются на разные объекты.


А теперь собственно о том, как можно "ударить молотком по пальцу". Придумаем более сложный пример. Есть TreeView, в нодах которого присутствует Tag, в котором в свою очередь хранится какое-то очень важное значение. И вот в один прекрасный момент нам надо получить все значения Tag для выбранных элементов дерева с приведением к конкретному типу.



public static IEnumerable<Output> Map<Input, Output>(
Converter<Input, Output> convert, IEnumerable<Input> container) {
foreach(Input item in container) {
yield return convert(item);
}

При помощи вот такого простенького Map'а я могу применять почти любое действие к почти любой коллекции. Например, могу написать метод, возвращающий значение Tag для узла дерева, и передать этот метод в Map вместе с коллекцией выбранных узлов дерева. И на выходе из Map у меня будет требуемая "коллекция". Казалось бы, ровно то, что и требовалось. Однако, всё не так просто. Как стало ясно из предыдущего примера, на выходе из Map'а получается не коллекция (я намеренно так назвал её и поставил кавычки) а итератор, который по натуре своей создание ленивое и не станет делать ничего до тех пор, пока его об этом явно не попросят. А попросить его о чём-то могут уже после того, как список выбранных узлов в дереве изменится. Вот Вам и грабли. Так что, ко всякому умному инструменту надо подходить с пониманием, даже если это простой и до боли знакомый итератор.


May 29, 2007

C# в стиле JavaScript

Пару дней назад я писал о том, как можно нетрадиционно использовать возможности анонимных делегатов в C# 2.0. То есть не то, чтобы нетрадиционно, но в стиле JavaScript.

Настало время посмотреть как подобное извращение влияет на скомпилированный код. Для этого напишем небольшой примерчик.

using System;

public class App{

private static Action<string> _actionAsDelegate = delegate(string text){
return;
};

private static void _actionAsStaticMethod(string text){
return;
}

private void _actionAsMethod(string text){
return;
}

public static void Main(string[] args){
_actionAsDelegate(string.Empty);
_actionAsStaticMethod(string.Empty);
(new App())._actionAsMethod(string.Empty);
}

}

На всякий случай объясню немножко, чего я пытаюсь сказать. Итак: надо выполнить какое-то действие над строкой. Первый вариант - это простой метод _actionAsMethod, а второй - _actionAsDelegate, который на самом деле всего лишь переменная, которой присвоен анонимный делегат. В JavaScript такое объявление вполне нормально и иногда даже более правильно, чем объявление функции стандартным путём. На всякий случай, я написал ещё _actionAsStaticMethod, просто для того, чтобы сравнить результаты.


Компилируем и смотрим, что скажет по этому поводу ILDASM.


.method public hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
// Code size 45 (0x2d)
.maxstack 8
IL_0000: nop
IL_0001: ldsfld class [mscorlib]System.Action`1<string> App::_actionAsDelegate
IL_0006: ldsfld string [mscorlib]System.String::Empty
IL_000b: callvirt instance void class [mscorlib]System.Action`1<string>::Invoke(!0)
IL_0010: nop
IL_0011: ldsfld string [mscorlib]System.String::Empty
IL_0016: call void App::_actionAsStaticMethod(string)
IL_001b: nop
IL_001c: newobj instance void App::.ctor()
IL_0021: ldsfld string [mscorlib]System.String::Empty
IL_0026: call instance void App::_actionAsMethod(string)
IL_002b: nop
IL_002c: ret
} // end of method App::Main

Сейчас сразу не вспомню где конкретно, но я где-то определённо читал, что анонимные делегаты превращаются компилятором в методы. А тут что-то совсем сложное. Одно ясно однозначно: вызов _actionAsDelegate дороже вызова _actionAsMethod, хотя бы потому, что он виртуальный. Для полной достоверности, надо бы, наверное, провести замеры времени при 1_000_000 итераций, но я этим заниматься не стану. Для меня главное - уяснить, что JavaScript'овые штучки в C# "не пройдут", что в общем-то, и правильно. Каждому - своё.


February 20, 2007

Проблемы с RSS и первый опыт Yahoo! Pipes

Не так давно я писал о том, как мой блог переехал на ITBLogs, и какие проблемы я решал всвязи с этим. Собственно, проблема была одна, как совместить 2 RSS потока в один. Тогда я принял решение в пользу сервиса FeedBite.

Я периодически смотрю статистику своего фида, и меня радуют числа подписчиков. Конечно, они не так высоки, как у популярных блоггеров, но всё таки, я не один. И вот сегодня я решил сам подписаться на свой фид. Не для статистики, а ради пущего контроля. И оказалось, что сделал я это совсем даже не зря. По непонятной пока что причине FeedBite не транслировал фид моего блога на ITBLogs, при этом фид, который предоставлен Blogger работал в штатном, как говорится, режиме. Более всего странно то, что по отдельности всё работет нормально, то есть RSS, сгенерированный Community Server'ом просматривается и содержит все самые последние посты, и FeedBite говорит, что прекрасно понимает формат подсовываемого ему фида, однако на выходе ничего.

И я решил найти новый способ решения данной проблемы. К тому же сейчас очень много говорят про Yahoo! Pipes. И я решил попробовать. Завёл себе Yahoo! ID (которого мне так недоставало, чтобы попробовать Flickr, а времени создать не было), залогинился и стал экспериментировать. Интересная, скажу я вам, штука. Правда, пока что есть не так много источников информации, и над ними можно производить не так много действий, но для моих нужд вполне достаточно, и даже сверх того. В конце концов вот что у меня получилось. Внутри всё просто: элемент Fetch, на вход которого попадают два фида моих блогов. Так как во времена зеркалирования в обоих фидах были одинаковые записи, следом за Fetch идёт блок Unique, работающий по полю Title. Затем, в качестве предосторожности, я поставил блок Sort, который сортирует записи по дате публикации в порядке убывания (хотя такое поведение присуще фидам само по себе). И с выхода сортировщика всё попадает на Pipe Output. Далее я откопал адрес фида только что созданного пайпа и прописал его в поле "источник" в мой FeedBurner фид.

Всё достаточно просто, а интерфейс работы с Pipes меня просто обрадовал. Красиво, быстро и удобно, хотя не всё продумано, и, как я уже отметил ранее, не хватает источников данных и операций. А вот если бы там был ещё и API, цены бы не было такому ресурсу.

В качестве окончания вот что... У меня есть просьба ко всем, кто подписан на мой фид через http://feeds.feedburner.com/Items откликнитесь, пожалуйста, или в комментариях или напрямую maxim.moiseev@gmail.com. Был ли провал в постах, связанный с переездом блога и что произошло сегодня после того, как я обновил фид? В теории агрегатор должен был высыпать всё содержимое "нового" фида, включая и те посты, которые были опубликованы уже давно здесь, и те, которые попали только в ITBlogs и не были оттранслированы в RSS по милости FeedBite. Заранее спасибо.

fd684997-5d56-49a2-af5f-788eb9e53da1


This page is powered by Blogger. Isn't yours?