Здравствуйте!
Вопрос по отладке приложений. Я написал Direct3D-шный screensaver.
Все работает стабильно, но иногда приложение зависает. Не могу найти закономерности.
Суть программы в показе примитивного слайд-шоу. Работает с одними и теми же файлами.
Изображения для слайд-шоу загружаются в отдельных потоках, потом синхронизируются, потом загружаются в текстуры.
Проверял process explorer-ом на предмет утечки памяти. Объем занимаемой памяти не увеличивается,
но постоянно растет page faults.
Я не очень опытный в вопросе работы с DirectX, возможно я неправильно освобождаю текстуры или что-нибудь еще…
Бывают ли утечки видео памяти? Если да, то как их проверить? Нужно ли освобождать текстуру и спрайт перед каждой загрузкой нового изображения?
Извините за изобилие вопросов, просто теряюсь в догадках. Основной вопрос: как найти ошибку?
Заранее благодарен,
Дмитрий.
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
21-01-2009 15:43
Да да да!
Так я и думал :)
Спасибо еще раз.
Вопрос решен.
Я редко пользуюсь дельфияскими графическими объектами, потому могу и ошибаться. Насколько помню, в конце петли выборки сообщений главного потока производится освобождение HDC всех незалоченных Canvas. Так как Вы рисуете в доп. потоке, то может случиться так, что HDC канвы Вашего битмапа будет освобожден как раз в момент рисования. Чтобы этого избежать, канву следует залочить. Только надо, видимо, вызовы Canvas.Lock и Unlock перенести в доп.поток.
begin
NewBitMap:= TBitmap(Msg.WParam); NewBitMap.Canvas.Lock;
try
FNameToLoad:= PChar(Msg.LParam);
StrDispose(PChar(Msg.LParam));
// тут делаем страшные операции с jpeg, NewBitMap, и Stream finally
NewBitMap.Canvas.Unlock;
end;
PostMessage(Form1.Handle, WM_THREAD_COMPLETE, 0, 0);
end
а в других местах убрать. Но точно я не помню, попробуйте.
Надеюсь, однако, что если я неправ, то более знающие люди меня поправят.
Есть еще один маленький вопрос по запуску потока на обработку
У меня все прекрасно работает без
FBitmap.Canvas.Lock;
здесь:
var
P: PChar;
begin
P:= StrAlloc(PChar(ListBox1.Items[Form1.ListBox1.ItemIndex]));
FBitmap.Canvas.Lock; //
PostThreadMessage(FMyThread.ThreadID, UM_NEWBITMAP, integer(FBitmap), integer(P));
// FBitmap здесь передавать не обязательно, его можно сразу указать при создании потока
end;
Дело в том, что в конце страшных операций с jpeg, NewBitMap, и Stream я получаю результат, который нужно передать в основной поток,
используя FBitmap.
Насколько я понимаю,
передает рабочему потоку ссылку на FBitmap? А рабочий поток, пытаясь нарисовать на канве FBitmap-а вызывает ошибку.
Зачем тогда лочить канву? Или ее нужно разлочить перед передачей результатов работы? Тогда теряться смысл блокировки, так как в любой момент процессорное время может быть переключено на основной поток.
Если это важно, то я запускаю только один экземпляр рабочего потока.
P.S. Спасибо огромное за код, с потоком, который постоянно работает. Операции стали производиться четче и быстрее. ;)
var
...
Msg: TMsg;
begin // Вот этот оператор привел меня в замешательсво
PeekMessage(..., PM_NOREMOVE);
// Здесь создаем t_bitmap, JPeg, стрим и все что надо
try
while GetMessage(Msg. 0, 0, 0) do
// Я втавил
if Msg.message = WM_NEWBITMAP then
//Вместо
// if Msg.msg = VM_NEWBITMAP then
//Это правильно?
begin
NewBitMap:= TBitmap(Msg.WParam);
FNameToLoad:= PChar(Msg.LParam);
StrDispose(PChar(Msg.LParam));
// тут делаем страшные операции с jpeg, NewBitMap, и Stream
PostMessage(Form1.Handle, WM_THREAD_COMPLETE, 0, 0);
end;
finally
// Здесь все созданное уничтожаем
// NewBitMap не трогать!!!
end;
Спасибо!
Я не силен в WinAPI, но общие представления имею.
Целый день провел в попытках сделать приведенный пример рабочим - пока тчетно :(
Есть вопросы:
var
P: PChar;
begin
P:= StrAlloc(PChar(ListBox1.Items[Form1.ListBox1.ItemIndex])); //Здесь, я так понимаю нужно добавить
StrPLCopy(P, ListBox1.Items[Form1.ListBox1.ItemIndex],
Length(ListBox1.Items[Form1.ListBox1.ItemIndex]));
FBitmap.Canvas.Lock;
PostThreadMessage(FMyThread.ThreadID, UM_NEWBITMAP, integer(FBitmap), integer(P));
// FBitmap здесь передавать не обязательно, его можно сразу указать при создании потока
// Я не очень понял как именно мы передаем FBitmap. Если можно краткий комент - это было бы здорово.
end;
Судя по коду, понятие "синхронизация потоков" Вам не знакомо:)
Не уверен, что правильно понял идею, но если понял то:
Не вижу смысла создавать каждый раз новый поток, как, впрочем, и остальные ресурсы.
Функция потока может выглядеть примерно так
var
...
Msg: TMsg;
begin
PeekMessage(..., PM_NOREMOVE);
// Здесь создаем t_bitmap, JPeg, стрим и все что надо
try
while GetMessage(Msg. 0, 0, 0) do
if Msg.Msg = UM_NEWBITMAP then
begin
NewBitMap:= TBitmap(Msg.WParam);
FNameToLoad:= PChar(Msg.LParam);
StrDispose(PChar(Msg.LParam));
// тут делаем страшные операции с jpeg, NewBitMap, и Stream
PostMessage(Form1.Handle, WM_THREAD_COMPLETE, 0, 0);
end;
finally
// Здесь все созданное уничтожаем
// NewBitMap не трогать!!!
end;
В приватную секцию формы добавить поля FMyThread: TMyThread и FBitmap: TBitmap, создание и инициализацию обоих разместить в OnCreate формы. Запуск потока на обработку выполнять примерно так:
var
P: PChar;
begin
P:= StrAlloc(PChar(ListBox1.Items[Form1.ListBox1.ItemIndex]));
FBitmap.Canvas.Lock; //
PostThreadMessage(FMyThread.ThreadID, UM_NEWBITMAP, integer(FBitmap), integer(P));
// FBitmap здесь передавать не обязательно, его можно сразу указать при создании потока
end;
А обработчик WM_THREAD_COMPLETE в форме выплнить так:
begin
NextTexture.Assign(FBitMap); // если это вообще нужно
FBitmap.Canvas.Unlock;
need_to_reload_textures := True;
end;
На OnDestroy формы уничтожаем сначала FMyThread, потом FBitmap
begin
PostThreadMessage(FMyThread.ThreadID, WM_QUIT, 0, 0);
FMyThread.Free;
FBitmap.Free;
...
end;
procedure TMyThread.Execute;
var
j, new_h, new_w, new_x, new_y: integer;
s: ansistring;
f_ext:string;
jpeg: TJpegImage;
Stream: TMemoryStream;
t_bitmap: TBitmap;
begin
Result:=true;
t_bitmap := TBitmap.Create;
NewBitMap:=TBitmap.Create;
with NewBitMap do
begin
PixelFormat:=pf24bit;
Width := ScreenWidth;
Height := ScreenHeight;
end;
jpeg := TJPEGImage.Create;
jpeg.CompressionQuality := 100;
Stream := TMemoryStream.Create;
try
try // тут делаем страшные операции с jpeg, NewBitMap, и Stream пока не выкладываю,
// думаю, они не играют ключевую роль
except
on E: Exception do
begin
Result:= false;
LogErrorThread(E.Message,'TMyThread.Execute');
end;
end;
if not Terminated then
begin
//Временное решение. Пока-что единственный способ, который дал мне результат. :(
if Assigned(Form1.NextTexture) then
Form1.NextTexture.Assign(NewBitMap);
PostMessage(Form1.Handle, WM_THREAD_COMPLETE, 0, 0);
end;
finally
if t_bitmap<>nil then
t_bitmap.Free;
if jpeg<>nil then
jpeg.Free;
if Stream<>nil then
Stream.Free;
if newbitmap<>nil then
newbitmap.Free;
end;
end;
Вот эта процедура запускается, когда рабочий поток посылает сообщение основному
procedure TForm1.HandleThreadCompletion(var Message: TMessage);
var St:TMemoryStream;
begin
if Assigned(MyThread1) then
begin
MyThread1.WaitFor;
try не работает :(
// form1.NextTexture.Assign(MyThread1.NewBitMap);
работает
// MyThread1.NewBitMap.SaveToFile('c:\x.bmp');
// form1.NextTexture.LoadFromFile('c:\x.bmp');
if not Assigned(MyThread1) then
begin
MyThread1:=TMyThread.Create(True);
MyThread1.FreeOnTerminate:=false;
try
with MyThread1 do
begin
// NewBitMap:=nil;
ScreenWidth:=Screen.Width;
ScreenHeight:=Screen.Height;
FNameToLoad := Form1.ListBox1.Items[Form1.ListBox1.ItemIndex];
Resume;
end;
except
on E: Exception do
begin
LogError(E.Message,'TForm1.Pict_Change');
MyThread1.Free;
MyThread1:=nil;
exit;
end;
end;
end;
и вот теперь есть проблема, которую не могу побороть: Я хотел считывать обработанный bitmap в TForm1.HandleThreadCompletion.
Но, при данном коде рабочего потока - MyThread.newbitmap.free будет выполнена, и не факт, что основной поток успеет обработать сообщение. Освободить TMyThread.newbitmap в TForm1.HandleThreadCompletion не удается.
Если ее не освобождать - происходит утечка.
Вообще "извлечь" данные TMyThread.newbitmap по окончанию TMyThread.Execute удается лишь извращенными способами вроде записи на жесткий диск или в Tmemorystream...
Подскажите пожалуйста, как поправить код, чтобы корректно присваивать Form1.NextTexture изображение полученное в MyThread1.NewBitmap...
поиск утечек, которые корректно освобождаются по завершению программы
Если память была выделена в процессе работ+ы, а при завершении программы корректно освобождена, то это строго говоря не утечка. Для любого инструмента контроля все будет выглядеть нормально. Другое дело, что с точки зрения логики это может быть неправильным, но это можете определить только Вы сами.
Проблема таки в организации многопоточной работы
Скорее всего у Вас происходит взаимоблокировка дыух или нескольких потоков. Типа поток 1 ждет, когда поток 2 выполнит действие A, а в это время поток 2 ждет, когда поток 1 выполнит действие B. Попробуйте определить место, на котором зависают Ваши потоки.
Спасибо!
Кажется, я близок к решению.
Проблема таки в организации многопоточной работы. :)
А возможно ли каким-то образом автоматизировать поиск утечек, которые корректно освобождаются по завершению программы?
Никак. FastMM может контролировать только ту память, которую Вы запрашиваете у него - GetMem и т.п. Память, выделенная помимо него, каким-нибудь LocalAlloc etc, им не контролируется.
>>>page faults = около 14 000 000
Скорее всего это означает интенсивный свопинг, во всяком случае другие причины лично мне не известны. А свопинг, в свою очередь, может косвенно свидетельствовать и о больших утечках памяти в Вашем приложении. Причем FastMM вполне может этих утечек не замечать. Во-первых, эта память может выделяться помимо него, а во-вторых, она может вполне корректно освобождаться - но при завершении приложения.
Добавил FastMM4.
В этом приложении происходят утечки памяти.
5-12 байт: Unknownx1
21-36 байт: TCriticalSection x 1
Как это связать с page faults?
Приложение зависает при значении page faults = около 14 000 000.
Я впервые сталкиваюсь с отладкой утечек памяти, поэтому прошу помочь, даже
в элементарных на первый взгляд вещах.
Кто-нибудь в курсе по поводу page faults?
Это единственное, что приходиться подозревать.
Очень внимательно пересмотрел структуру потоков.
Перечитал http://forum.vingrad.ru/forum/topic-60076.html
Может есть особенности в конструкциях try - except, при использовании их в потоках?
Да нет, вроде как не потоки. Дело в том, что я отображаю изображения с жесткого диска. Их не много и когда последний из списка показан, программа отображает первый.
т.е. действия цикличны, а зависает программа по истечении произвольного количества времени.
Это приводит к мысли о воздействии внешних факторов (кол-во) свободной памяти, место на HDD, или кол-во доступной видео памяти.
Можно ли как-то проверить наличие не освобожденных указателей? Ведь при загрузки текстуры резервируется видеопамять...
Не могу найти закономерности. Вероятнее всего, ошибка при организации многопоточной работы. Попробуйте запускать всё в одном потоке. Либо методом половинного деления: закомнтариваете половину кода, запускаете, смотрите есть ли ошибка. Если есть, закоментариваете половину оставшийся части. Если ошибка исчезла, значит она в последнем закоментированном участке...
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.