-Поиск по дневнику

Поиск сообщений в XMs_00

 -Подписка по e-mail

 




О скорости рисования

Суббота, 29 Июня 2013 г. 01:30 + в цитатник

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

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

Создадим приложение, рисующее символы с 0x00 по 0xFF. Конечно, всё, что ниже 0x20, а также 0x7F, будет отображаться символами-заменителями из шрифтового набора. Наследуемся от банального, базового для большей части графики, QWidget. Объявим две переменные, в которых будем хранить высоту и ширину одного символа:

qreal m_charHeight, m_charWidth;
. Нам это нужно для облегчения позиционирования. Опционально можно добавить ещё одну, для хранения размера шрифта:
int m_pointSize;
, хотя вполне можно обойтись и без неё. Добавим функцию, в которой рассчитаем всю метрику заранее, чтобы не тратить на это время при рисовании:

void textDrawingLags::updateMetrics()
{
 QFont font("Monospace", m_pointSize);
 QFontMetricsF fm(font);
 font.setStyleHint(QFont::TypeWriter);
 setFont(font);

 m_charHeight = fm.height() + fm.lineSpacing();
 m_charWidth  = fm.width(' ');
}

Теперь объявим слот, в котором будем устанавливать размер шрифта. Для незнающих: слот — это такая функция, которую можно соединить с сигналом в Qt.

void textDrawingLags::setPointSize(int size)
{
 if (size > 0) m_pointSize = size;
 updateMetrics();
 update();
}

Ну а теперь самое вкусное — рисование. Для этого нам потребуется переопределить метод paintEvent(). Выводить символы будем самым банальным способом, а именно вложенным циктом. Для того, чтобы выяснить, где же находится «бутылочное горлышко», будем считать время вывода каждого символа. Для этого воспользуемся функцией clock_gettime() из sys/time.h. В качестве первого аргумента требуется передать идентификатор определённых часов, над которыми производится действие, в нашем случае — считывание. Мы будем использовать CLOCK_MONOTONIC. Это (дальше копипаста из мана) «Часы, которые не могут быть настроены и показывают монотонный ход времени отсчитываемой с некой неопределённой начальной точки. Эти часы не подвержены скачкам системного времени (например, системный администратор вручную изменил время), но на них влияет постепенная подгонка, выполняемая adjtime(3) и NTP.». Конечно, можно использовать CLOCK_MONOTONIC_RAW, которые не подвержены влиянию подгонок, но они linux-only. Второй аргумент — указатель на структуру timespec, в которую будет производиться запись. В этой структуре всего два поля:

struct timespec {
   time_t   tv_sec;        /* секунды */
   long     tv_nsec;       /* наносекунды */
};

Таким образом мы можем получить время иполнения функции с наносекундной точностью с помощью всего лишь двух вызовов clock_gettime() и вычитания соответствующих полей. Реализуем:

void textDrawingLags::paintEvent(QPaintEvent *event)
{
 enum
  {
   lineWidth = 50
  };

QPainter painter(this);

timespec before, after;

for (int row = 0; row <= 255 / lineWidth; row++)
{
for (int col = 0; col < lineWidth; col++)
{
uchar byte = row * lineWidth + col;
clock_gettime(CLOCK_MONOTONIC, &before);
painter.drawText(QPointF(col * m_charWidth, (row + 1) * m_charHeight),
QString(byte));
clock_gettime(CLOCK_MONOTONIC, &after);
qDebug() << byte << (after.tv_sec - before.tv_sec) * 1000000000 +
(after.tv_nsec - before.tv_nsec);
}
}
}

Теперь добавим возможность менять размеры шрифта, чтобы хоть как-то наблюддать динамику. Я для этого создал объект класса QSlider и соединил его сигнал valueChanged(int) с нашим слотом setPointSize(int). Теперь то, ради чего вся эта простыня и писалась — результаты. Результаты у нас пишутся в qDebug, то есть на обычный stdout. Смотрим:

0 51693106
1 35842765
2 420025
3 158798
4 126142
5 105458
6 128760
7 106825
8 115162
9 6657
10 118186
11 165867
12 128326
13 193952
14 156774
15 139513
16 147840
17 184469
18 198668
19 85604
20 70287
21 267185
22 107105
23 168136
24 62846
25 62421
26 250656
27 99162
28 91953
29 92568
30 133928
31 136564
32 45095
33 53572
34 45311
35 70583
36 101214
37 106024
38 95820
39 41662
40 57484
41 57898
42 60790
43 52232
44 44646
45 40653
46 49764
47 56786
48 115682
49 61661
50 80952
51 90818
52 65721
53 82000
54 95470
55 57595
56 146830
57 119405
58 51554
59 52923
60 54534
61 49117
62 57989
63 81870
64 116935
65 65074
66 89938
67 95563
68 80013
69 54673
70 54934
71 79745
72 66030
73 52478
74 61751
75 61632
76 49524
77 65483
78 58218
79 76829
80 68419
81 81803
82 75548
83 84900
84 49986
85 68637
86 54861
87 81385
88 61369
89 56447
90 54748
91 49213
92 51255
93 49743
94 48846
95 48197
96 44827
97 93237
98 86417
99 112983
100 87983
101 83439
102 69115
103 100498
104 93019
105 63990
106 68632
107 65611
108 62726
109 110372
110 65117
111 78333
112 92659
113 86230
114 62984
115 86394
116 67677
117 68326
118 81760
119 58653
120 57333
121 65114
122 53602
123 77994
124 47639
125 78767
126 61234
127 180531
128 162950
129 142011
130 152447
131 162468
132 130440
133 162478
134 163232
135 156861
136 79776
137 100022
138 120916
139 88942
140 99420
141 127758
142 110148
143 116442
144 110934
145 112756
146 97278
147 117253
148 170246
149 116075
150 130326
151 142536
152 112694
153 146690
154 189348
155 146780
156 166172
157 103917
158 122802
159 139194
160 56284
161 57718
162 93039
163 74795
164 79437
165 70965
166 56739
167 96955
168 37001
169 94416
170 67998
171 48890
172 36413
173 19951
174 98955
175 32334
176 52198
177 46522
178 52801
179 62222
180 33843
181 59815
182 52273
183 33565
184 62539
185 54271
186 77509
187 57288
188 123035
189 89543
190 91207
191 60943
192 55690
193 52373
194 53856
195 84775
196 57824
197 68316
198 55117
199 77634
200 53032
201 49985
202 51272
203 51443
204 48754
205 47656
206 50469
207 49997
208 76117
209 92504
210 86221
211 85137
212 83127
213 84593
214 70014
215 44781
216 74964
217 63597
218 61466
219 91106
220 66139
221 54194
222 57550
223 75739
224 77963
225 88955
226 81367
227 90817
228 78814
229 127927
230 85558
231 80403
232 76231
233 74422
234 75322
235 76760
236 57286
237 52173
238 62556
239 55853
240 81528
241 92197
242 154674
243 87059
244 87566
245 101799
246 110573
247 58270
248 104020
249 77297
250 79248
251 81264
252 80658
253 82314
254 87915
255 125297

Подобная разница объясняет, как банальная инверсия способна вопреки теории ускорить отрисовку. Причём это моя домашняя тачка, а на рабочей разница была ещё заметней. В качестве лекарства от тормозов (хоть и сомнительного в плане оправданности) может служить замена всех байт с 0x00 по 0x1F, а также 0x7F, на что-нибудь более быстрое в плане отрисовки. На точку, например. Таким образом получится выигрыш в скорости, а также дополнительный бонус в виде большей наглядности. Разумеется, речь идёт только о «чистом» отображении. В шестнадцатеричном и прочих рисуются обычные символы, а посему дополнительные оптимизации не требуются.


// Возможно, я изобретаю велосипед, и всё на самом деле решается гораздо проще. Если кто подскажет лучшее решение — буду только рад. Полный набор исходников:

main.cpp: 
#include <QtGui/QApplication>
#include <QSlider>
#include "textDrawingLags.h"



int main(int argc, char** argv)
{
QApplication app(argc, argv);
textDrawingLags foo;
QSlider pointSizeSlider;
pointSizeSlider.setRange(7, 16);
QObject::connect(&pointSizeSlider, SIGNAL(valueChanged(int)),
&foo, SLOT(setPointSize(int)));
foo.show();
pointSizeSlider.show();
return app.exec();
}

textDrawingLags.h:
#ifndef textDrawingLags_H
#define textDrawingLags_H

#include <QWidget>



class QPaintEvent;

class textDrawingLags : public QWidget
{
Q_OBJECT

private:
qreal m_charWidth,
m_charHeight;
int m_pointSize;

void updateMetrics();

protected:
void paintEvent(QPaintEvent *event);

public:
textDrawingLags();
virtual ~textDrawingLags();

public slots:
void setPointSize(int size);
};

#endif // textDrawingLags_H

textDrawingLags.cpp:
#include "textDrawingLags.h"

#include <QPaintEvent>
#include <QPainter>
#include <sys/time.h>
#include <QDebug>



textDrawingLags::textDrawingLags()
{
setPointSize(font().pointSize());
}



textDrawingLags::~textDrawingLags()
{}



/***********
* Private *
***********/
void textDrawingLags::updateMetrics()
{
QFont font("Monospace", m_pointSize);
QFontMetricsF fm(font);
font.setStyleHint(QFont::TypeWriter);
setFont(font);

m_charHeight = fm.height() + fm.lineSpacing();
m_charWidth = fm.width(' ');
}



/*************
* Protected *
*************/
void textDrawingLags::paintEvent(QPaintEvent *event)
{
enum
{
lineWidth = 50
};

QPainter painter(this);

timespec before, after;

for (int row = 0; row <= 255 / lineWidth; row++)
{
for (int col = 0; col < lineWidth; col++)
{
uchar byte = row * lineWidth + col;
clock_gettime(CLOCK_MONOTONIC, &before);
painter.drawText(QPointF(col * m_charWidth, (row + 1) * m_charHeight),
QString(byte));
clock_gettime(CLOCK_MONOTONIC, &after);
qDebug() << byte << (after.tv_sec - before.tv_sec) * 1000000000 +
(after.tv_nsec - before.tv_nsec);
}
}
}



/****************
* Public slots *
****************/
void textDrawingLags::setPointSize(int size)
{
if (size > 0) m_pointSize = size;
updateMetrics();
update();
}



#include "textDrawingLags.moc"


Понравилось: 31 пользователям

Поиск сообщений в XMs_00
Страницы: [1] Календарь