После приобретения SMP материнской платы с процессора я решил провести испытания SiSoft Sandra 2005 Lite. Она показала такие результаты моей машины:
Motherboard: i815 (Solano)
Momery: PC133
CPU: 2x Pentium III 933MHz
CPU: Dhrystone 6718, Whetstone 2607;
Multimedia: Integer x4 iSSE 16113, Floating-Point x4 iSSE 20253;
Мemory Bandwidth: RAM Bandwith Int Buffered iSSE 689, RAM Bandwith Floating-Point Buffered iSSE 673;
Показатели памяти такие плохии потому, что это PC133. RAMBUS, например, в 4 раза быстрее. К примеру, по словам сандры, моя машина точно такая по скорости как Pentium 2.4 ГГц. Возникает вопрос: зачем платить больше? Отсюда вывод - SMP - это хорошо.
Я думаю все сталкивались с проблемой в виндовсе когда на форме куча белых квадратов, мыша не двигается, а в это время на винчестере происходят разные вещи. Это от того что у людей которые пишут программы нету многопоточной культуры.
Представим себе ГиперМега полезную программу, которая, например, читает\пишет какие-то файлы. Играясь, по ходу написания программы, в основном сосредоточившись на алгортме обработки, был написан для него ГУИ интерфейс. Как запустить алгоритм ?, - допустим кнопкой. Например алгоритм - СделатьПолезное. Хорошо если данные маленькие, а если большие ? Тут вы скажете, что это всего-лишь 2% случаев, и доводку можно списать на суппорт или как исправление багов. Но именно это всегда причина того что как говорят неискушенные "виндовс залипает". Все потому, что люди не понимают что такое потоки и их важность. Или увидели в книге "используйте потоки осторожно" и боятся их юзать. Чужь. Юзать нужно почти всегда и везде.
Допустим у нас жесткие условия, чем сложнее алгоритм - тем он проще. Как заставить писать в потоковом режиме используя буферизацию писать в разные места на диске большие объемы данных ? Очень просто - while (true) printf("xxxxxxxxxxxxx"); Вы себе не представляете какая махина за этим стоит - очереди, Interrupt Request Packets, семиуровневая подель шины драйверов, многопоточные драйвера файловой системы, буферизация. Вообщем не будем изобретать велосипед, а заюзаем эту махину, все равно ничего круче чем в NT не видел. Это и будет наш алгоритм - СделатьПолезное. В первом приближении, представьте себе, что это конвертор AVI.
using System;
using System.IO;
using System.Windows.Forms;
using System.Threading;
public class ГиперМега : Form {
public void УбийцаВремени() {
StreamWriter Полезное = new StreamWriter("ТутЛежитПолезное");
while (true) Полезное.Write("Буратина был тупой ");
}
public void ПередайДальше(object sender, EventArgs e) {
Controls[0].Enabled = false;
УбийцаВремени();
}
public ГиперМега() {
Text = "УльтраСупер";
Button батон = new Button();
батон.Text = "Поджигай";
батон.Click += new EventHandler(ПередайДальше);
Controls.Add(батон);
}
public static void Main() {
ГиперМега СуперУльтра = new ГиперМега();
Application.Run(СуперУльтра);
}
}
Ну вот и написали нашу чудную программу. Хочу вас попросить что бы вы компилировали ее как консольную, почему увидите позже. Поджигаем. И сразу пробуем переместить окошко. Получилось ? Хрен. Почему ? Потому что наш алгоритм СделатьПолезное превратился в УбийцуВремени. Заблокировал поток в котом рисуется ON_PAINT а также где обрабатываются все другие сообщения. Нажмите ^C в консоли. На крест не поможет. За 10 секунд эта прога создст у вас около 50 МБ. Вообщем .NET в этом плане молодец.
А представите теперь что это обычный обработчик кнопки на который вы используете какое-то чужое АПИ, например ходите в базу, или еще какой-то. Вы уверены, что "там" не дойдет до while (true) ? В большенстве случаев как раз именно и на этом "залипают" все проги.
"I шо його робити дорога редакція?". Да всего лишь вынести в поток обработчик.
using System;
using System.IO;
using System.Windows.Forms;
using System.Threading;
public class ГиперМега : Form {
Thread Worker;
public void СделатьПолезное() {
StreamWriter Полезное = new StreamWriter("ТутЛежитПолезное");
while (true) { Полезное.Write("Буратина был тупой "); }
}
public void ПередайДальше(object sender, EventArgs e) {
Controls[0].Enabled = false;
Worker = new Thread(new ThreadStart(СделатьПолезное));
Worker.Start();
}
public ГиперМега() {
Text = "УльтраСупер";
Button батон = new Button();
батон.Text = "Поджигай";
батон.Click += new EventHandler(ПередайДальше);
Controls.Add(батон);
}
protected override void OnClosed(EventArgs e) {
Worker.Abort(); // освободите ресурс
}
public static void Main() {
Application.Run(new ГиперМега());
}
}
И делайте это как можно чаще. И что все обработчики кнопок выносить в отдельные потоки? Я отвечу. Дело в том что архитектура винды не разделяет окно на два потока, один который реагирует на внутренние сообщения, а другой на сообщения пользователя. От этого вся ерунда. Поэтому каждому програмимсту надо изобретать велосипед от "залипания". Можете например в гугле поискать "Worker Controller UI Thread" там написано как нужно разделять оконные и пользовательские потоки в .NET.
Кстати замечу, что в этом примере, надо освобождаться от ресурсов. Потому что поток это такой хитрый ресурс который если не остановить прямо, навсегда потеряется в недрях CLR до Collect и долго еще вам будет писать на винчестер пока диск не кончиться =)
Добавлю, что во-первых, из-за того, что в BeOS для каждой формы отдельный ГИУ поток (он называется picasso), она ведетсебя так мягко. И инвалидных регионов закрашеных белым вы там никогда не увидите. Даже если запустить 1000 программ, все равно все будем мягко, с каждым разом все медленне и медленне но все будет плавно. А описаных выше проблем там нет. Во вторых люди которые писали приложения для BeOS не ленились нигде проверять количество процессоров в системе и исходя из этого создавать количество потоков. Согласитесь, для большинства, кто пишет под виндовс и в голову такое не придет. Вообщем, тема - многопоточная культура =)
Я вдруг решил что хватит рахваливать BeOS, а то все уже начали думать что лучше ничего быть не может. Да, ребята из Be сделали много, но многое не успели, а некоторые вещи даже не планировали. Главное чего нету в BeOS, и что есть в промышленных серверных современных ОС - это две вещи: асинхронный ввод-ввывод и файлы проецируемые в память.
Asynchronous I/O. BeOS абсолютно не поддерживает асинхронный ввод вывод. Хотя показатели пропукной способности файловой системы а так же время отклика очень хорошие как для синхронного ввода/вывода. Даже Linux 2.6 начал поддерживать aio.
Memory Mapped I/O. Да, в BeOS напрочь отсутсвует файлы проецируемые в память. Вследсвии чего, многоие трюки, которые так нравятся всем кто любит NT, тут сделать нельзя. Вообщем красивее всего это сделано в NT.
Почему же инжинеры из Be, создавая систему с таким тюнингом планировщика и файловой системы заточеной под выполнение операции с mass data не позаботились об этих вариантах ввода-вывода. Может не хотели, или все таки решили что этих феничек нахер не надо ? Не думаю. Впрочем хочу отметить что лайв видео, захват и операции с большими файлами даются BeOS действительно очень легко. В чем фишка ?
Console MT (Console Multithreaded) Прожект
Я помню когда у меня впервые появилась дуальная система я первым делом написал консольное приложение, которое в двух потоках выводило символы "о" и "х", позже я написал сортировку слияинием, но это другая история. Итак, я решил ничего нового не придумывать, а опробывать (вспомнить) "мягкость" планировщика. Для сравнения я взял
BeOS, Windows 2003 Server и Linux 2.4. Правда в линуксе у меня только консольный режим (поэтому он как бы вне соревнований я просто упомяну о нем). Еще раз напомню что система представляет собой два Pentium III 933 MHz.
КомпетишнВот код для BeOS.
#include
#include
#include
#include
int32 th1(void *data) { while (1) { printf("o"); fflush(stdout); } }
int32 th2(void *data) { while (1) { printf("x"); fflush(stdout); } }
int main(void)
{
status_t exit_value;
int32 data1 = 0;
int32 data2 = 0;
int32 i1 = spawn_thread(th1, "T1", B_REAL_TIME_DISPLAY_PRIORITY, &data1);
int32 i2 = spawn_thread(th2, "T2", B_REAL_TIME_DISPLAY_PRIORITY, &data2);
resume_thread(i1);
resume_thread(i2);
wait_for_thread(i1, &exit_value);
// never reach here
printf("Console MT. version 1.0/BeOS");
return 0;
}
Этот тест в основном нагружает подсистему фреймбуфера если запускается в графическом консольном окне или файловую систему если перенаправить вывод в файл. Эта довольно примитивная по сути программа, но она с первого взгляда дает понять какие приоритеты ставили перед собой разработчики планировщика.
Вот версия для Windows.
#include "stdafx.h" // сомнительный инклуд
#include
#include
#include
#include
unsigned __stdcall ThreadOne( void* pArguments )
{ while (1) { printf("x"); fflush(stdout); } return 0; }
unsigned __stdcall ThreadTwo( void* pArguments )
{ while (1) { printf("o"); fflush(stdout); } return 0; }
int main(int argc, char* argv[])
{
DWORD params;
unsigned ThreadOneId;
unsigned ThreadTwoId;
HANDLE th1 = (HANDLE)_beginthreadex(
NULL, 0, &ThreadOne, NULL, 0, &ThreadOneId );
HANDLE th2 = (HANDLE)_beginthreadex(
NULL, 0, &ThreadTwo, NULL, 0, &ThreadTwoId );
WaitForSingleObject(th1, -1);
return 0;
}
Вообщем для виндовса тема такая: для всех вариантов (однопоточные, многопоточные, libc, напрмер) разный стафф - вот и здесь вместо CreateThread надо писать обернутую _beginthreadex с чем не валится libc от майкрософта. Под линукс код не выложил, но он тоже достаточно прост, используется нативные pthreads.
Тестирование
BeOS
Сначала я тестировал BeOS версию. Потоки создавал с приоритетом B_REAL_TIME_DISPLAY_PRIORITY. Сначала запустил с двумя процессорами потом с одним. С двумя все прозаично, на выводе четкое чередование "хо":
хохохохохохохохо....
С одним немного интереснее. Но сразу видно что кванты выдаются маленькие.
xxxxxoooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxoooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxoooooo
Причем такое чередование в три-четыре линии довольно стабильное.
Потом я перенаправил все это дело в файл и получил приблизительно то же самое. Для двух процессоров было все таже неизменная последовательность "хохохохо". Для однопроцессорного запуска три-четыре линии сменились на четыре-пять. Вообщем не так уж и много, в целом можно сказать что чувствительность у файловой системы к многопоточным приложениям очень высокая.
Я остался доволен. Потом решил немного пришпорить лошадей и понизил приоритет с B_REAL_TIME_DISPLAY_PRIORITY до B_DISPLAY_PRIORITY. Для однопроцессорного запуска линии немного "удлиннились" а для двухпроцессорного получилась такая картина:
ooooooooxoxooxoxxxooxxxxxxoxxxxoxxoooxooooxxxxx
xooooxoxoooxoxooooxxxxxoxoooxoxoxxxxxxooooxooox
oooxoxxoooooxxxxxxoxoooxxxxoxoxxxxxxoxoxxoooooo
oooooooooooooxxxxooxxooooooxxxxxoxoxoooooxxxxxo
xxxoxxxoxooxooooooooooxoooxooxooxxoooxoxoooooxo
xxxoxooooxoxxxxooooooxoooxxxoxooxxxxxoooooxoxoo
oxoxxxxxxooooxxxxxoxxxxxxxxoooooxooxxxoxxooooox
xxxxoxxxoooxoooooooxooxooooooooooxoxoooxxxoxooo
oxxxxxxxxxxxxxxxxxxxoxooooooxoooooxxxxoooxoxoox
xxoxxoxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxoooooxxxxx
xooooooxooooxoxxxxxxxooooooxooooooooooxoxxooooo
xxoooooxooxxxxxxxooooxoooxxxoxxxoxxxoxxxxxoxxxo
oooxoxxxxxxoooooxxoooxxooooooxoooooxooxoxxxxoxx
xoooooxoooxxooooxxxxxxxxxxxxxxxxxxxxxxxxxoooxxx
oxxxxxxoxxxxxxooxoooxxoxxxxxxoooooxoooooxoxxxxx
xoooooxoooxoxxxoxoxxoxxxxxxxxxxxooxoxxoxxxxxooo
oxoxoooxxoooxxxxxoxxxoooxxxoxooxooooooooooxoxxx
oxooxoooooxxxxoxxoooxxoxxxxxxoxxxxxoxxoxoooxoxx
oxxxxxxxxxxxoooooxxxoxooxoxxxoxxxxxxxoooooooooo
ooooooooxoxxoooooxxxxxoooxoooxoooxooooxxxoooxxx
oxxxooxooooooxooooxoxxxooxxoooooxxxoxoxoxxxxxxo
xxxxxoxoooxxxooooooxxxxxoxxxooooxoxxoxxoxxxoooo
Вообщем система на потоки с таким приоритетом, как видно, откличкается неохотно. С файлами та же ситуация.
Windows
У меня на работе стоит Celeron 2.4 GHz. Вот что выдает Windows на этой однопроцессорной машине.
xxxxxoooooooooooooooooxxxxxxxxxxxxxxxxxoooooooo
oooooooooxxxxxxxxxxxxxxxxxoooooooooooooooooxxxx
xxxxxxxxxxxxxoooooooooooooooooxxxxxxxxxxxxxxxxx
ooooooooooooooxxxxxxxxxxxxxxxxxoooooooooooooooo
oxxxxxxxxxxxxxxxxxoooooooooooooooooxxxxxxxxxxxx
xxxxxoooooooooooooooooxxxxxxxxxxxxxxxxxoooooooo
oooooooooxxxxxxxxxxxxxxxxxoooooooooooooooooxxxx
xxxxxxxxxxxxxoooooooooooooooooxxxxxxxxxxxxxxxxx
oooooooooooooooooxxxxxxxxxxxxxxxxxooooooooooooo
Неплохо, скажете вы, мало чем отличается от БиОС. Ан нет. Во первых частота больше, во вторых давайте посмотрим как себя ведет файловая система. Результат после долей секунды такой: Файл размером 39 КБ состоящий из трех частей (по 13 Кб) иксы, нули, иксы. Вообщем синхронный ввод/вывод не в сравнение с BeOS.
На этом месте один мой товарищ http://shvydky.blogspot.com сказал мне: "Конечно будет плохо. Ты ведь не используеш IoCompletionPort!" И предложил такой вариант программы на до-диез:
using System;
using System.Threading;
public class test
{
public static void Method1(object state) {
char ch = (char)state;
while (true) Console.Write(ch);
}
public static void Main() {
ThreadPool.QueueUserWorkItem(new WaitCallback(Method1), 'x');
ThreadPool.QueueUserWorkItem(new WaitCallback(Method1), 'o');
Console.ReadLine();
}
}
Не обдумав все хорошенько я сразу запустил приложение на работе на своем однопроцессорном целероне. Результат еще хуже (чем просто нативные потоки):
oooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooooooxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxooooooooooo
oooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxo
oooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooooooxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Во первых, я сначала засомневался если ли вообще здесь IoCompetionPort, так как IoCompletionPort связывается с хэндлом файла, а в коде нигде такого связывания нет. Во вторых, оказывается реализация пула потоков в CLR даже на Windows NT не юзает IoCompletionPort. За детальными комментариями прошу обращаться к:
http://weblogs.asp.net/kavitak/archive/2003/12/15/43581.aspx.
Нужно переписать приложение используя нативные IoCompletionPort, но хочу предсказать что результат все равно будет хуже чем "просто пишущие два потока".
На двухпроцессорных машинах с счастью ставильная последовательность "хохохохо", чего не скажешь о Линуксе. Там даже на двухпроцессорной конфигурации имели место быть разные паттерны. Вообщем забегая вперед скажу что у Линукса самый плохой планировщик. Я не привожу сдесь линукс потому что в текстовой консоли он выводит сволочь как бешеный эти кресты и нули. Вывод в файл же настолько отстает от Windows и BeOS, что я даже не хочу сюда приводить результаты (естественно имеется ввиду мягкость вывода иксов и нулей).