Skip to content

Instantly share code, notes, and snippets.

@t-mat
Last active October 11, 2015 10:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save t-mat/3846536 to your computer and use it in GitHub Desktop.
Save t-mat/3846536 to your computer and use it in GitHub Desktop.
libjpeg-turbo 1.2.1 を VC++2012 でビルドする&ちょっと速度改善

libjpeg-turbo 1.2.1 を VC++2012 でビルドする

短いまとめ

  • libjpeg-turbo に付属する BUILDING.txt に従ってビルドする
  • そのままだと元の libjpeg/simd よりも遅いので、テキトーに修正する

前準備

  • CMake をデフォルト設定でインストールする
  • nasm をデフォルト設定でインストールする
  • libjpeg-turbo 1.2.1 のソースコード libjpeg-turbo-1.2.1.tar.gz をダウンロードする

ソースコードを展開し、CMake を使ってプロジェクトファイルを生成する

libjpeg-turbo-1.2.1.tar.gz を展開して、必要なツールにパスを通す

7za x libjpeg-turbo-1.2.1.tar.gz
7za x libjpeg-turbo-1.2.1.tar
set PATH="%ProgramFiles(x86)%\Cmake 2.8\bin";%PATH%

cmake を実行し、VC++2012 用のプロジェクト libjpeg-turbo.sln を生成する

cd libjpeg-turbo-1.2.1
cmake -G "Visual Studio 11"

VC++2012 でプロジェクトの設定を変更し、ビルドする

生成された libjpeg-turbo.sln を VC++2012 で開く

start libjpeg-turbo.sln

simd プロジェクトの実行ファイルパスに nasm を追加する

  • Visual Studio のメニューから「表示(V)」-「ソリューションエクスプローラー(P)」を選択し、「ソリューションエクスプローラー」を表示する
  • 「ソリューションエクスプローラー」内にある simd を右クリックし、「プロパティ(R)」を選択する
    • 「simd プロパティページ」ダイアログが出る
    • 左上の「構成(C)」リストボックスから、「すべての構成」を選択する
    • 左ペインのツリービュー上で、「構成プロパティ」-「VC++ ディレクトリ」を選択する
    • 右ペインにある「全般」-「実行可能ファイル ディレクトリ」の右端にある逆三角形(▼)ボタンを押し、プルダウンメニューから「<編集...>」を選択する
      • 「実行可能ファイルディレクトリ」ダイアログが出る
      • ダイアログの右上にあるフォルダボタン「新しい行」をクリックする
      • 追加された空行に nasm をインストールしたフォルダ (nasm.exe の存在するフォルダ) を入力する
        • デフォルトのままインストールしたのであれば $(ProgramFiles)\nasm など
      • 入力後、「実行可能ファイルディレクトリ」ダイアログの右下の「OK」ボタンを押す
    • 「simd プロパティページ」ダイアログの右下の「OK」ボタンを押す

実際のビルド

  • ALL_BUILD のデバッグ版およびリリース版をビルド
  • Debug および Release ディレクトリに各種 DLL、ライブラリ、実行ファイル、デバッグ情報が生成される

遅いんですが…

速くなりそうなオプションをつけても…

	cinfo.quantize_colors		= FALSE;
	cinfo.two_pass_quantize		= FALSE;
	cinfo.dither_mode		= JDITHER_NONE;
	cinfo.dct_method		= JDCT_FASTEST;
	cinfo.do_fancy_upsampling	= FALSE;
  • libjpeg-turbo の元となったコード libjpeg/simd を直接リンクした場合と比較して 80% ぐらいの速度
    • I/O を除いたメモリリード、メモリライトのみで比較
  • /LTCG, /GL あたりを付けると 90% ぐらいまで改善される
  • 複数ファイルをマルチスレッド展開すれば 98% ぐらいまで改善されるけど…

遅い原因と解消方法

  • simd と turbo の速度の差は jdhuff.c : decode_mcu() の動作速度の差が原因
  • jdhuff.c : decode_mcufast() の entropy->ac_needed[blkn] が TRUE のケースのブロックの動作速度の差が大きい
  • ので、無理やり simd 版のほうの jdhuff.c, jdphuff.c, jdhuff.h を turbo 側に上書きすれば速くなる
  • コードの中身は見る元気が無いのでスルー

トレースメッセージの呼び出しを削除する

速度は変わらないが、開発以外ではいらないので削除する。

以下の変更を加えた後、ビルド定義で LIBJPEG_NO_TRACEMS#define することで、 トレース用の jpeg_error_mgr::emit_message の呼び出しを抑制する。

// jerror.h
	#define TRACEMSS(cinfo,lvl,code,str)  \
	  ((cinfo)->err->msg_code = (code), \
	   strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \
	   (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)))

+	#if defined(LIBJPEG_NO_TRACEMS)
+	#undef TRACEMS
+	#undef TRACEMS1
+	#undef TRACEMS2
+	#undef TRACEMS3
+	#undef TRACEMS4
+	#undef TRACEMS5
+	#undef TRACEMS8
+	#undef TRACEMSS
+	#define TRACEMS(...)
+	#define TRACEMS1(...)
+	#define TRACEMS2(...)
+	#define TRACEMS3(...)
+	#define TRACEMS4(...)
+	#define TRACEMS5(...)
+	#define TRACEMS8(...)
+	#define TRACEMSS(...)
+	#endif

	#endif /* JERROR_H */

コードブロック別の速度メモ

libjpeg-turbo でのシングルスレッド/SIMD(SSE2) デコード時のコードブロック別の時間を計測

  • てきとうに集めた 100MBytes 分の JPEG をメモリ→メモリ間デコード
huffman     1060.7ms  54.0%  jdmainct.c : process_data_simple_main() および process_data_context_main() の (*cinfo->coef->decompress_data)
iDCT         405.2ms  20.6%  jdcoefct.c : decompress_onepass() 内の (*inverse_DCT)
upsample      47.2ms   2.4%  jdsample.c : sep_upsample() 内の (*upsample->methods[ci])
color conv   243.9ms  12.4%  jdsample.c : sep_upsample() 内の (*cinfo->cconvert->color_convert)
misc         200.0ms  10.2%  メインループや cinfo の初期化など
total       1965.4ms
  • 普通にブロック別にスレッドを分けた場合、1.5倍速(-35%) 程度までの速度の改善が見込める
    • この場合、ハフマンデコードがボトルネックになる

検索して出てきたアイディア:

  • RST で分けたらどうだろう?
    • RST は無い場合もけっこう多いので一般的な画像には効果は薄い(?)
    • 自分でエンコード&デコードをする場合は検討の余地がある
  • Parallel Huffman Decoding with Applications to JPEG Files
    • とりあえずデータをハフマンコードとみなしてデコードして、失敗したらリトライしよう、というもの
  • CUDA JPEG Library
    • RST を利用してパラレルに実行するのでフツーといえばフツー。RST が少ないとうまくパラレルにできていないという警告が出る
// $ sudo apt-get install libboost-all-dev
// $ sudo apt-get install libjpeg-turbo8-dev
// g++ -std=c++0x jpeg_test.cpp -ljpeg -lboost_filesystem -lboost_system -lrt -pthread
// cp some/where/*.jpeg ./data
// ./a.out --output ./data # output test
// ./a.out --multi-thread ./data # memory-to-memory decompression benchmark
#define _CRT_SECURE_NO_WARNINGS // VC++ : prevent nonsense warnings
#include <assert.h>
#include <stdio.h>
#include <vector>
#include <string>
#include <future> // std::async
//
#if defined(_WIN32)
#include <filesystem> // std::tr2:sys
#include <windows.h>
#include "jpeglib.h"
namespace tr2_filesystem = std::tr2::sys;
static uint64_t getMicrosec() {
uint64_t t;
QueryPerformanceCounter((LARGE_INTEGER*) &t);
static uint64_t freq = 0;
static uint64_t origin = 0;
if(freq == 0) {
origin = t;
QueryPerformanceFrequency((LARGE_INTEGER*) &freq);
freq /= 1000;
}
return ((t - origin) * 1000) / freq;
}
static std::string getFilenameString(const std::tr2::sys::path& path) {
// MSVC++2012's <filesystem> is incompatible with TR2:
// Filesystem Library for C++11/TR2 (Revision 1)
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3335.html
return path.filename();
}
#elif defined(__linux__)
#include <time.h> // clock_gettime
#include <boost/filesystem.hpp> // boost::filesystem
#include <jpeglib.h> // libjpeg-turbo
namespace tr2_filesystem = boost::filesystem;
static uint64_t getMicrosec() {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC_RAW, &t);
return (uint64_t) (t.tv_sec * 1000*1000) + (t.tv_nsec / 1000);
}
static std::string getFilenameString(const boost::filesystem::path& path) {
return path.filename().string();
}
#else
# error abort.
#endif
static_assert(BITS_IN_JSAMPLE == 8, "BITS_IN_JSAMPLE must be '8'");
static_assert(sizeof(JSAMPLE) == 1, "sizeof(JSAMPLE) must be '1'");
//
template<typename I, typename F>
void para_for_each(I first, I last, const F& func) {
auto n = last - first;
std::vector<decltype(std::async(func, *first))> vh(n);
for(decltype(n) i = 0; i < n; ++i) { // fork
vh[i] = std::async(func, first[i]);
}
for(decltype(n) i = 0; i < n; ++i) { // join
vh[i].get();
}
}
//
struct File : public std::vector<uint8_t> {
std::string filename;
bool load(const std::string& filename) {
bool ret = false;
this->filename = filename;
if(FILE* fp = fopen(filename.c_str(), "rb")) {
fseek(fp, 0, SEEK_END);
resize(ftell(fp));
rewind(fp);
if(fread(&(*this)[0], 1, size(), fp) == size()) {
ret = true;
}
fclose(fp);
}
return ret;
}
};
//
struct FileEntries : std::vector<std::string> {
void addDirExtFile(const std::string& filepath, const std::string& ext) {
using namespace tr2_filesystem;
path dirPath(filepath);
if(exists(dirPath)) {
for(directory_iterator it(dirPath), it_end; it != it_end; ++it) {
if(is_regular_file(it->status()) && extension(it->path()) == ext) {
std::string f = getFilenameString(it->path());
push_back(filepath + "/" + f);
}
}
}
}
};
//
class MyMemoryDstManager {
public:
MyMemoryDstManager() {
clear();
}
~MyMemoryDstManager() {
}
MyMemoryDstManager(const struct jpeg_decompress_struct* cinfo) {
clear();
if(cinfo->quantize_colors) {
//
} else {
bytesPerPixel = cinfo->out_color_components;
bufferStride = cinfo->output_width * bytesPerPixel;
bufferHeight = cinfo->output_height;
ioBuffer.resize(bufferStride * bufferHeight);
pixrow = (JSAMPROW) &ioBuffer[0];
}
}
void clear() {
bufferStride = 0;
bufferHeight = 0;
bytesPerPixel = 1;
pixrow = nullptr;
}
JSAMPROW* getDstBuffer() {
return &pixrow;
}
size_t getDstBufferHeight() const {
return 1;
}
const void* getIoBuffer() {
if(ioBuffer.empty()) {
return nullptr;
} else {
return &ioBuffer[0];
}
}
int getIoBufferStride() const {
return bufferStride;
}
int getIoBufferBytesPerPixel() const {
return bytesPerPixel;
}
int getIoBufferWidth() const {
return bufferStride / bytesPerPixel;
}
int getIoBufferHeight() const {
return bufferHeight;
}
void putPixelRows(JDIMENSION rowsSupplied) {
auto p = (char*) pixrow;
if(p) {
p += bufferStride * rowsSupplied;
pixrow = (JSAMPROW) p;
}
}
protected:
size_t bufferStride;
size_t bufferHeight;
size_t bytesPerPixel;
std::vector<char> ioBuffer;
JSAMPROW pixrow;
};
//
class MyMemorySrcManager {
public:
MyMemorySrcManager() {
clear();
}
~MyMemorySrcManager() {
}
MyMemorySrcManager(const void* memory, size_t memoryBytes) {
clear();
pub.init_source = [](j_decompress_ptr cinfo) {
auto that = (MyMemorySrcManager*) (cinfo->src);
return that->init_source(cinfo);
};
pub.fill_input_buffer = [](j_decompress_ptr cinfo) {
auto that = (MyMemorySrcManager*) (cinfo->src);
return that->fill_input_buffer(cinfo);
};
pub.skip_input_data = [](j_decompress_ptr cinfo, long num_bytes) {
auto that = (MyMemorySrcManager*) (cinfo->src);
return that->skip_input_data(cinfo, num_bytes);
};
pub.term_source = [](j_decompress_ptr cinfo) {
return;
};
pub.resync_to_restart = jpeg_resync_to_restart; // use default method
memoryCurrent = (const char*) memory;
memoryEnd = memoryCurrent + memoryBytes;
}
void clear() {
pub.bytes_in_buffer = 0;
pub.next_input_byte = nullptr;
memoryCurrent = nullptr;
memoryEnd = nullptr;
}
struct jpeg_source_mgr* getJpegSourceMgr() {
return &pub;
}
void init_source(j_decompress_ptr cinfo) {
}
boolean fill_input_buffer(j_decompress_ptr cinfo) {
auto nBytes = memoryEnd - memoryCurrent;
if(nBytes > 0) {
pub.next_input_byte = (const JOCTET*) memoryCurrent;
pub.bytes_in_buffer = nBytes;
memoryCurrent += nBytes;
} else {
static uint8_t fakeEoi[2] = { 0xff, JPEG_EOI };
pub.next_input_byte = &fakeEoi[0];
pub.bytes_in_buffer = sizeof(fakeEoi);
}
return TRUE;
}
void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
if(num_bytes > 0) {
while(num_bytes > (long) pub.bytes_in_buffer) {
num_bytes -= (long) pub.bytes_in_buffer;
(void) fill_input_buffer(cinfo);
}
pub.next_input_byte += (size_t) num_bytes;
pub.bytes_in_buffer -= (size_t) num_bytes;
}
}
protected:
struct jpeg_source_mgr pub;
const char* memoryCurrent;
const char* memoryEnd;
};
//
class MyErrorManager {
public:
MyErrorManager() {
jpeg_std_error(&jerr);
jerr.error_exit = [](j_common_ptr cinfo) {
auto that = (MyErrorManager*) (cinfo->err);
return that->errorExit(cinfo);
};
jerr.emit_message = [](j_common_ptr cinfo, int msg_level) {
auto that = (MyErrorManager*) (cinfo->err);
return that->emitMessage(cinfo, msg_level);
};
jerr.output_message = [](j_common_ptr cinfo) {
auto that = (MyErrorManager*) (cinfo->err);
return that->outputMessage(cinfo);
};
jerr.format_message = [](j_common_ptr cinfo, char* buffer) {
auto that = (MyErrorManager*) (cinfo->err);
return that->formatMessage(cinfo, buffer);
};
jerr.reset_error_mgr = [](j_common_ptr cinfo) {
auto that = (MyErrorManager*) (cinfo->err);
return that->resetErrorMgr(cinfo);
};
}
struct jpeg_error_mgr* getJpegErrorMgr() {
return &jerr;
}
void errorExit(j_common_ptr cinfo) {
outputMessage(cinfo);
jpeg_destroy(cinfo);
throw jerr.msg_code;
}
void emitMessage(j_common_ptr cinfo, int msgLevel) {
if(msgLevel < 0) {
// It's a warning message. Since corrupt files may generate many warnings,
// the policy implemented here is to show only the first warning,
// unless trace_level >= 3.
if(jerr.num_warnings == 0 || jerr.trace_level >= 3) {
outputMessage(cinfo);
}
jerr.num_warnings += 1;
} else {
// It's a trace message. Show it if trace_level >= msg_level.
if(jerr.trace_level >= msgLevel) {
outputMessage(cinfo);
}
}
}
void outputMessage(j_common_ptr cinfo) {
char buffer[JMSG_LENGTH_MAX];
formatMessage(cinfo, buffer);
fprintf(stderr, "%s\n", buffer);
}
void formatMessage(j_common_ptr cinfo, char* buffer) {
const char * msgtext = nullptr;
{
int msg_code = jerr.msg_code;
if(msg_code > 0 && msg_code <= jerr.last_jpeg_message) {
msgtext = jerr.jpeg_message_table[msg_code];
}
if(!msgtext) {
jerr.msg_parm.i[0] = msg_code;
msgtext = jerr.jpeg_message_table[0];
}
}
boolean isstring = FALSE;
{
const char * msgptr = msgtext;
char ch;
while((ch = *msgptr++) != '\0') {
if(ch == '%') {
if(*msgptr == 's') {
isstring = TRUE;
}
break;
}
}
}
if(isstring) {
sprintf(buffer, msgtext, jerr.msg_parm.s);
} else {
sprintf(buffer, msgtext,
jerr.msg_parm.i[0], jerr.msg_parm.i[1],
jerr.msg_parm.i[2], jerr.msg_parm.i[3],
jerr.msg_parm.i[4], jerr.msg_parm.i[5],
jerr.msg_parm.i[6], jerr.msg_parm.i[7]);
}
}
void resetErrorMgr(j_common_ptr cinfo) {
jerr.num_warnings = 0;
jerr.msg_code = 0;
}
protected:
struct jpeg_error_mgr jerr; // public fields
};
//
int main(int argc, const char* argv[]) {
bool bFileOut = false;
bool bMultiThread = false;
FileEntries files;
for(int iArg = 1; iArg < argc; ++iArg) {
const std::string o(argv[iArg]);
if(o == "--output") {
bFileOut = true;
} else if(o == "--multi-thread") {
bMultiThread = true;
} else {
files.addDirExtFile(o, ".jpg");
files.addDirExtFile(o, ".jpeg");
}
}
std::vector<File> vFile(files.size());
for(size_t i = 0; i < files.size(); ++i) {
std::string filename = files[i];
File& f = vFile[i];
f.load(filename);
printf("#%3d : %-32s %10d Bytes\r", i, filename.c_str(), f.size());
}
printf("\n");
uint64_t totalInput = 0;
uint64_t totalOutput = 0;
auto cl = [bFileOut, &totalInput, &totalOutput](const File& f) {
try {
struct jpeg_decompress_struct cinfo = { 0 };
MyErrorManager errMan;
cinfo.err = errMan.getJpegErrorMgr();
jpeg_create_decompress(&cinfo);
cinfo.quantize_colors = FALSE;
cinfo.two_pass_quantize = FALSE;
cinfo.dither_mode = JDITHER_NONE;
cinfo.dct_method = JDCT_FASTEST;
cinfo.do_fancy_upsampling = FALSE;
MyMemorySrcManager srcMan(&f[0], f.size());
cinfo.src = srcMan.getJpegSourceMgr();
jpeg_read_header(&cinfo, TRUE);
jpeg_calc_output_dimensions(&cinfo);
MyMemoryDstManager dstMan(&cinfo);
jpeg_start_decompress(&cinfo);
while(cinfo.output_scanline < cinfo.output_height) {
JDIMENSION n = jpeg_read_scanlines(
&cinfo, dstMan.getDstBuffer(), dstMan.getDstBufferHeight());
dstMan.putPixelRows(n);
}
jpeg_finish_decompress(&cinfo);
totalInput += f.size();
totalOutput += dstMan.getIoBufferStride() * dstMan.getIoBufferHeight();
if(bFileOut && dstMan.getIoBuffer()) {
std::string dstFilename = f.filename + ".ppm";
if(FILE* fp = fopen(dstFilename.c_str(), "wb")) {
int width = dstMan.getIoBufferWidth();
int height = dstMan.getIoBufferHeight();
auto buf = dstMan.getIoBuffer();
auto bufBytes = dstMan.getIoBufferStride() * height;
fprintf(fp, "P6\n%d %d\n%d\n", width, height, 255);
fwrite(buf, 1, bufBytes, fp);
fclose(fp);
}
}
jpeg_destroy_decompress(&cinfo);
} catch(int msg_code) {
printf("[%s]: JPEG Decoder error occured (%d).\n"
, f.filename.c_str(), msg_code);
}
};
auto t0 = getMicrosec();
if(bMultiThread) {
para_for_each(begin(vFile), end(vFile), cl);
} else {
std::for_each(begin(vFile), end(vFile), cl);
}
auto totSec = (double) (getMicrosec() - t0) / 1000.0 / 1000.0;
auto totInpMb = (double) totalInput / 1024.0 / 1024.0;
auto totOutMb = (double) totalOutput / 1024.0 / 1024.0;
printf("total files =%10d files\n", vFile.size());
printf(" time =%10.5f msec\n", totSec * 1000.0);
printf(" input =%10.5f MBytes\n", totInpMb);
printf(" output =%10.5f MBytes\n", totOutMb);
printf("avg. input =%10.5f MBytes/sec\n", totInpMb / totSec);
printf(" output =%10.5f MBytes/sec\n", totOutMb / totSec);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment