Skip to content

Commit 0121f53

Browse files
author
Dane Springmeyer
committed
refactor and speed up hextree image encoding by using dense_has_map - refs mapnik#1629
1 parent 6a2f562 commit 0121f53

21 files changed

+3014
-60
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ test-local:
4141
export MAPNIK_INPUT_PLUGINS_DIRECTORY=`pwd`/plugins/input/ && \
4242
make test
4343

44+
bench:
45+
@export ${LINK_FIX}=`pwd`/src:${${LINK_FIX}} && \
46+
./benchmark/run
47+
4448
check: test-local
4549

4650
demo:

SConstruct

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,8 @@ if not HELP_REQUESTED:
17561756
if env['SVG_RENDERER']:
17571757
SConscript('tests/cpp_tests/svg_renderer_tests/build.py')
17581758

1759+
SConscript('benchmark/build.py')
1760+
17591761
# install pkg-config script and mapnik-config script
17601762
SConscript('utils/mapnik-config/build.py')
17611763

benchmark/allocation.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include <mapnik/geometry.hpp>
2+
#include <mapnik/wkt/wkt_factory.hpp>
3+
4+
5+
#define BOOST_CHRONO_HEADER_ONLY
6+
#include <boost/chrono/process_cpu_clocks.hpp>
7+
#include <boost/chrono.hpp>
8+
#include <boost/thread/thread.hpp>
9+
10+
using namespace boost::chrono;
11+
using namespace mapnik;
12+
13+
void threaded_benchmark(void test(),std::string const& name, unsigned threads) {
14+
using namespace boost::chrono;
15+
typedef process_cpu_clock clock_type;
16+
process_real_cpu_clock::time_point start = process_real_cpu_clock::now();
17+
boost::thread_group threads;
18+
for (unsigned i=0;i<threads;++i)
19+
{
20+
threads.create_thread(test);
21+
}
22+
threads.join_all();
23+
clock_type::duration elapsed = process_real_cpu_clock::now() - start;
24+
std::clog << boost::chrono::duration_cast<milliseconds>(elapsed)
25+
<< " (" << boost::chrono::duration_cast<seconds>(elapsed) << ")"
26+
<< " <-- " << name << "\n";
27+
}
28+
29+
30+
void test_wkt_creation()
31+
{
32+
boost::ptr_vector<mapnik::geometry_type> paths;
33+
mapnik::wkt_parser parse_wkt;
34+
std::string value("GEOMETRYCOLLECTION(MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),(30 20, 20 25, 20 15, 30 20))),POINT(2 3),LINESTRING(2 3,3 4),MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),(30 20, 20 25, 20 15, 30 20))),MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),(30 20, 20 25, 20 15, 30 20))),MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),(30 20, 20 25, 20 15, 30 20))))");
35+
if (!parse_wkt.parse(value, paths)) std::clog << "failed to parse\n";
36+
int iterations = 10000;
37+
typedef process_cpu_clock clock_type;
38+
process_real_cpu_clock::time_point start = process_real_cpu_clock::now();
39+
for (int i=0;i<iterations;++i) {
40+
parse_wkt.parse(value, paths);
41+
}
42+
clock_type::duration elapsed = process_real_cpu_clock::now() - start;
43+
std::clog << "elapsed: " << boost::chrono::duration_cast<milliseconds>(elapsed) << "\n";
44+
}
45+
46+
int main( int, char*[] )
47+
{
48+
return 0;
49+
}

benchmark/build.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import os
2+
import glob
3+
from copy import copy
4+
5+
Import ('env')
6+
7+
test_env = env.Clone()
8+
9+
test_env['LIBS'] = copy(env['LIBMAPNIK_LIBS'])
10+
test_env.AppendUnique(LIBS='mapnik')
11+
#test_env.AppendUnique(LIBS='sqlite3')
12+
test_env.AppendUnique(CXXFLAGS='-g')
13+
14+
for cpp_test in glob.glob('run*.cpp'):
15+
name = cpp_test.replace('.cpp','')
16+
source_files = [cpp_test]
17+
test_env_local = test_env.Clone()
18+
test_program = test_env_local.Program(name, source=source_files, LINKFLAGS=env['CUSTOM_LDFLAGS'])
19+
Depends(test_program, env.subst('../src/%s' % env['MAPNIK_LIB_NAME']))
20+
# build locally if installing
21+
if 'install' in COMMAND_LINE_TARGETS:
22+
env.Alias('install',test_program)
64.4 KB
Loading
64.4 KB
Loading

benchmark/data/multicolor.png

256 KB
Loading

benchmark/run.cpp

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#include <mapnik/graphics.hpp>
2+
#include <mapnik/image_data.hpp>
3+
#include <mapnik/image_util.hpp>
4+
#include <mapnik/image_reader.hpp>
5+
6+
7+
// stl
8+
#include <string>
9+
#include <iostream>
10+
#include <fstream>
11+
#include <sstream>
12+
13+
// boost
14+
#include <boost/shared_ptr.hpp>
15+
#include <boost/make_shared.hpp>
16+
17+
#define BOOST_CHRONO_HEADER_ONLY
18+
#include <boost/chrono/process_cpu_clocks.hpp>
19+
#include <boost/chrono.hpp>
20+
#include <boost/thread/thread.hpp>
21+
22+
using namespace boost::chrono;
23+
using namespace mapnik;
24+
25+
typedef process_cpu_clock clock_type;
26+
typedef clock_type::duration dur;
27+
28+
template <typename T>
29+
void benchmark(T test, std::string const& name)
30+
{
31+
if (!test.validate()) throw std::runtime_error(std::string("test did not validate: ") + name);
32+
dur elapsed = test.run();
33+
std::clog << name << ": " << boost::chrono::duration_cast<milliseconds>(elapsed) << "\n";
34+
}
35+
36+
bool compare_images(std::string const& src_fn,std::string const& dest_fn)
37+
{
38+
std::auto_ptr<mapnik::image_reader> reader1(mapnik::get_image_reader(dest_fn,"png"));
39+
if (!reader1.get())
40+
{
41+
throw mapnik::image_reader_exception("Failed to load: " + dest_fn);
42+
}
43+
boost::shared_ptr<image_32> image_ptr1 = boost::make_shared<image_32>(reader1->width(),reader1->height());
44+
reader1->read(0,0,image_ptr1->data());
45+
46+
std::auto_ptr<mapnik::image_reader> reader2(mapnik::get_image_reader(src_fn,"png"));
47+
if (!reader2.get())
48+
{
49+
throw mapnik::image_reader_exception("Failed to load: " + src_fn);
50+
}
51+
boost::shared_ptr<image_32> image_ptr2 = boost::make_shared<image_32>(reader2->width(),reader2->height());
52+
reader2->read(0,0,image_ptr2->data());
53+
54+
image_data_32 const& dest = image_ptr1->data();
55+
image_data_32 const& src = image_ptr2->data();
56+
57+
unsigned int width = src.width();
58+
unsigned int height = src.height();
59+
if ((width != dest.width()) || height != dest.height()) return false;
60+
for (unsigned int y = 0; y < height; ++y)
61+
{
62+
const unsigned int* row_from = src.getRow(y);
63+
const unsigned int* row_to = dest.getRow(y);
64+
for (unsigned int x = 0; x < width; ++x)
65+
{
66+
if (row_from[x] != row_to[x]) return false;
67+
}
68+
}
69+
return true;
70+
}
71+
72+
struct test1
73+
{
74+
unsigned iter_;
75+
76+
explicit test1(unsigned iterations) :
77+
iter_(iterations) {}
78+
79+
bool validate()
80+
{
81+
return true;
82+
}
83+
84+
dur run()
85+
{
86+
mapnik::image_data_32 im(256,256);
87+
std::string out;
88+
process_cpu_clock::time_point start = process_cpu_clock::now();
89+
for (int i=0;i<iter_;++i) {
90+
out.clear();
91+
out = mapnik::save_to_string(im,"png");
92+
}
93+
return process_cpu_clock::now() - start;
94+
}
95+
};
96+
97+
struct test2
98+
{
99+
unsigned iter_;
100+
boost::shared_ptr<image_32> im_;
101+
102+
explicit test2(unsigned iterations) :
103+
iter_(iterations),
104+
im_()
105+
{
106+
std::string filename("./benchmark/data/multicolor.png");
107+
std::auto_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(filename,"png"));
108+
if (!reader.get())
109+
{
110+
throw mapnik::image_reader_exception("Failed to load: " + filename);
111+
}
112+
im_ = boost::make_shared<image_32>(reader->width(),reader->height());
113+
reader->read(0,0,im_->data());
114+
}
115+
116+
bool validate()
117+
{
118+
std::string expected("./benchmark/data/multicolor-hextree-expected.png");
119+
std::string actual("./benchmark/data/multicolor-hextree-actual.png");
120+
mapnik::save_to_file(im_->data(),actual, "png8:m=h");
121+
return compare_images(actual,expected);
122+
}
123+
124+
dur run()
125+
{
126+
std::string out;
127+
process_cpu_clock::time_point start = process_cpu_clock::now();
128+
for (int i=0;i<iter_;++i) {
129+
out.clear();
130+
out = mapnik::save_to_string(im_->data(),"png8:m=h");
131+
}
132+
return process_cpu_clock::now() - start;
133+
}
134+
};
135+
136+
137+
138+
int main( int, char*[] )
139+
{
140+
try
141+
{
142+
std::cout << "starting benchmark…\n";
143+
{
144+
test1 runner(100);
145+
benchmark(runner,"encoding blank image as png");
146+
}
147+
148+
{
149+
test2 runner(100);
150+
benchmark(runner,"encoding multicolor image as png8:m=h");
151+
}
152+
153+
std::cout << "...benchmark done\n";
154+
return 0;
155+
}
156+
catch (std::exception const& ex)
157+
{
158+
std::clog << "test error: " << ex.what() << "\n";
159+
return -1;
160+
}
161+
}

benchmark/utils/random_image.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import mapnik
2+
import random
3+
4+
im = mapnik.Image(256,256)
5+
6+
for x in xrange(0,im.width()):
7+
for y in xrange(0,im.height()):
8+
r = int(random.random() * 255)
9+
g = random.random() * 255
10+
b = random.random() * 255
11+
a = random.random()
12+
color = mapnik.Color('rgba(%i,%i,%i,%f)' % (r,g,b,a))
13+
im.set_pixel(x,y,color)
14+
15+
im.save('./benchmark/data/multicolor.png')

0 commit comments

Comments
 (0)