Skip to content

Commit 7b55041

Browse files
committed
Refactor:maptool:Compress the tiles of a map in parallel
Phase 13 writes the zip file and needs 58 to 66 percent of the time of a conversion. Measurements on five country maps show that the phase does almost nothing but deflate: it moves 5.6 to 7.3 MB/s, and an isolated benchmark of zlib level 9 on real tile data gives 6.0 MB/s. The tiles of a slice do not depend on each other, and process_slice() already holds the whole slice in memory. This change compresses thread_count tiles at the same time and then writes them in the order of the list. write_zipmember() is now two functions. zip_compress_member() computes the CRC and the compressed data. It only reads zip_info, so threads can call it at the same time. write_zipmember_compressed() writes the result and must keep the order of the members, because the position of a member in the file depends on the members before it. The output does not change. The order of the members and the parameters of deflate stay the same. A run with one thread gives a file that is identical to the file of the version before this change, byte for byte. A run with 8 or 20 threads gives a file in which every member has a correct CRC. A batch holds no more than thread_count tiles, so the memory for the compressed data stays small against the memory for the slice. Measurement of the compression of a map of Luxembourg, 117.8 MB of tile data on a machine with 20 cores: threads time factor 1 18.4 s 1.00 4 8.1 s 2.24 8 4.9 s 3.55 20 3.0 s 6.03
1 parent 88c943c commit 7b55041

3 files changed

Lines changed: 193 additions & 43 deletions

File tree

navit/maptool/maptool.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,32 @@ void index_init(struct zip_info *info, int version);
412412
void index_submap_add(struct tile_info *info, struct tile_head *th);
413413

414414
/* zip.c */
415+
416+
/**
417+
* One zip member after the compression and before the write.
418+
*
419+
* zip_compress_member() fills this struct and write_zipmember_compressed()
420+
* writes it. The two steps are separate because the compression of different
421+
* members can run at the same time, but the write cannot.
422+
*/
423+
struct zip_member {
424+
/** The data to write. This is buffer, or the input when deflate did not help. */
425+
char *data;
426+
/** Size of data in bytes. */
427+
int data_size;
428+
/** Size of the member before the compression. */
429+
int uncomp_size;
430+
/** CRC-32 of the data before the compression. */
431+
int crc;
432+
/** 8 for deflate, 0 for no compression. */
433+
int method;
434+
/** The buffer for the compressed data. write_zipmember_compressed() frees it. */
435+
char *buffer;
436+
};
437+
415438
void write_zipmember(struct zip_info *zip_info, char *name, int filelen, char *data, int data_size);
439+
void zip_compress_member(struct zip_info *zip_info, char *data, int data_size, struct zip_member *m);
440+
void write_zipmember_compressed(struct zip_info *zip_info, char *name, int filelen, struct zip_member *m);
416441
int zip_write_index(struct zip_info *info);
417442
int zip_write_directory(struct zip_info *info);
418443
struct zip_info *zip_new(void);

navit/maptool/misc.c

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -300,11 +300,92 @@ int phase4(FILE **in, int in_count, int with_range, char *suffix, FILE *tilesdir
300300
return phase34(&info, zip_info, in, NULL, in_count, with_range);
301301
}
302302

303+
/** One member of a batch that process_slice() compresses in parallel. */
304+
struct slice_compress_job {
305+
struct zip_info *zip_info;
306+
struct tile_head *th;
307+
struct zip_member member;
308+
GThread *thread;
309+
};
310+
311+
static gpointer slice_compress_worker(gpointer data) {
312+
struct slice_compress_job *job = data;
313+
zip_compress_member(job->zip_info, job->th->zip_data, job->th->total_size, &job->member);
314+
return NULL;
315+
}
316+
317+
/**
318+
* @brief Compresses and writes the tiles of one slice.
319+
*
320+
* The compression is the largest part of this phase and one member does not
321+
* depend on another member, so this function compresses thread_count members at
322+
* the same time. It then writes the members of the batch in the order of the
323+
* list, because the position of a member in the zip file depends on the members
324+
* before it.
325+
*
326+
* The result is the same file that a sequential run gives. The order of the
327+
* members and the parameters of deflate do not change.
328+
*
329+
* A batch holds no more than thread_count members. The memory that the
330+
* compressed data needs is therefore small against slice_data, which already
331+
* holds the whole slice.
332+
*
333+
* @param zip_info the zip file that receives the tiles
334+
* @param tiles the tiles of the slice, in the order of the write
335+
* @param count the number of tiles
336+
*
337+
* @returns the number of tiles that went into the zip file
338+
*/
339+
static int write_tiles(struct zip_info *zip_info, struct tile_head **tiles, int count) {
340+
/* thread_count comes from the command line, so do not trust it for the
341+
* stride of the loop below. A stride of 0 would never end. */
342+
int batch_max = thread_count > 0 ? thread_count : 1;
343+
struct slice_compress_job *jobs = g_malloc0(sizeof(struct slice_compress_job) * batch_max);
344+
int maxnamelen = zip_get_maxnamelen(zip_info);
345+
gint64 start = g_get_monotonic_time();
346+
long long bytes = 0;
347+
int zipfiles = 0;
348+
int first, i;
349+
350+
for (first = 0; first < count; first += batch_max) {
351+
int batch = MIN(batch_max, count - first);
352+
for (i = 0; i < batch; i++) {
353+
jobs[i].zip_info = zip_info;
354+
jobs[i].th = tiles[first + i];
355+
jobs[i].thread = NULL;
356+
/* A tile without a name goes to the index and needs no compression. */
357+
if (jobs[i].th->name[0])
358+
jobs[i].thread = g_thread_new("zip_compress_worker", slice_compress_worker, &(jobs[i]));
359+
}
360+
for (i = 0; i < batch; i++) {
361+
if (jobs[i].thread)
362+
g_thread_join(jobs[i].thread);
363+
}
364+
for (i = 0; i < batch; i++) {
365+
struct tile_head *th = jobs[i].th;
366+
if (jobs[i].thread) {
367+
bytes += th->total_size;
368+
write_zipmember_compressed(zip_info, th->name, maxnamelen, &(jobs[i].member));
369+
zipfiles++;
370+
} else {
371+
dbg_assert(fwrite(th->zip_data, th->total_size, 1, zip_get_index(zip_info)) == 1);
372+
}
373+
}
374+
}
375+
g_free(jobs);
376+
377+
fprintf(stderr, "Compressed %d tiles, " LONGLONG_FMT " bytes in %.1f s with %d threads\n", zipfiles, bytes,
378+
(g_get_monotonic_time() - start) / 1000000.0, batch_max);
379+
380+
return zipfiles;
381+
}
382+
303383
static int process_slice(FILE **in, FILE **reference, int in_count, int with_range, long long size, char *suffix,
304384
struct zip_info *zip_info) {
305385
struct tile_head *th;
386+
struct tile_head **tiles;
306387
char *slice_data, *zip_data;
307-
int zipfiles = 0;
388+
int zipfiles = 0, tile_count = 0;
308389
struct tile_info info;
309390
int i;
310391

@@ -332,20 +413,23 @@ static int process_slice(FILE **in, FILE **reference, int in_count, int with_ran
332413
info.tilesdir_out = NULL;
333414
phase34(&info, zip_info, in, reference, in_count, with_range);
334415

416+
for (th = tile_head_root; th; th = th->next) {
417+
if (th->process)
418+
tile_count++;
419+
}
420+
tiles = g_malloc(sizeof(struct tile_head *) * (tile_count ? tile_count : 1));
421+
tile_count = 0;
335422
for (th = tile_head_root; th; th = th->next) {
336423
if (!th->process)
337424
continue;
338-
if (th->name[0]) {
339-
if (th->total_size != th->total_size_used) {
340-
fprintf(stderr, "Size error '%s': %d vs %d\n", th->name, th->total_size, th->total_size_used);
341-
exit(1);
342-
}
343-
write_zipmember(zip_info, th->name, zip_get_maxnamelen(zip_info), th->zip_data, th->total_size);
344-
zipfiles++;
345-
} else {
346-
dbg_assert(fwrite(th->zip_data, th->total_size, 1, zip_get_index(zip_info)) == 1);
425+
if (th->name[0] && th->total_size != th->total_size_used) {
426+
fprintf(stderr, "Size error '%s': %d vs %d\n", th->name, th->total_size, th->total_size_used);
427+
exit(1);
347428
}
429+
tiles[tile_count++] = th;
348430
}
431+
zipfiles = write_tiles(zip_info, tiles, tile_count);
432+
g_free(tiles);
349433
g_free(slice_data);
350434

351435
return zipfiles;

navit/maptool/zip.c

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,60 @@ static int compress2_int(Byte *dest, uLongf *destLen, const Bytef *source, uLong
8181
}
8282
#endif
8383

84-
void write_zipmember(struct zip_info *zip_info, char *name, int filelen, char *data, int data_size) {
84+
/**
85+
* @brief Compresses the data of one zip member.
86+
*
87+
* This function reads zip_info but does not change it. Several threads can
88+
* therefore compress different members at the same time. The caller must then
89+
* give the results to write_zipmember_compressed() in the order of the members,
90+
* because the position of a member in the file depends on the members before it.
91+
*
92+
* @param zip_info the zip file that receives the member
93+
* @param data the data of the member
94+
* @param data_size the size of data in bytes
95+
* @param m receives the result. Give it to write_zipmember_compressed().
96+
*/
97+
void zip_compress_member(struct zip_info *zip_info, char *data, int data_size, struct zip_member *m) {
98+
m->data = data;
99+
m->data_size = data_size;
100+
m->uncomp_size = data_size;
101+
m->buffer = NULL;
102+
m->crc = crc32(crc32(0, NULL, 0), (unsigned char *)data, data_size);
103+
m->method = zip_info->compression_level ? 8 : 0;
104+
#ifdef HAVE_ZLIB
105+
if (zip_info->compression_level) {
106+
uLongf destlen = data_size + data_size / 500 + 12;
107+
int error;
108+
m->buffer = g_malloc(destlen);
109+
error = compress2_int((Byte *)m->buffer, &destlen, (Bytef *)data, data_size, zip_info->compression_level);
110+
if (error == Z_OK) {
111+
if (destlen < data_size) {
112+
m->data = m->buffer;
113+
m->data_size = destlen;
114+
} else
115+
m->method = 0;
116+
} else {
117+
/* Note that this keeps method 8 for data that is not compressed.
118+
* The behavior is the same as before the members were compressed in
119+
* parallel. deflate needs more room than the buffer only for data
120+
* that grows, and tile data always becomes smaller. */
121+
fprintf(stderr, "compress2 returned %d\n", error);
122+
}
123+
}
124+
#endif
125+
}
126+
127+
/**
128+
* @brief Writes one compressed zip member and frees its buffer.
129+
*
130+
* Call this function for the members of a slice in the order of the members.
131+
*
132+
* @param zip_info the zip file that receives the member
133+
* @param name the name of the member
134+
* @param filelen the length that every name in this file uses
135+
* @param m the result of zip_compress_member(). This function frees its buffer.
136+
*/
137+
void write_zipmember_compressed(struct zip_info *zip_info, char *name, int filelen, struct zip_member *m) {
85138
struct zip_lfh lfh = {
86139
0x04034b50, 0x0a, 0x0, 0x0, zip_info->time, zip_info->date, 0x0, 0x0, 0x0, filelen, 0x0,
87140
};
@@ -95,35 +148,16 @@ void write_zipmember(struct zip_info *zip_info, char *name, int filelen, char *d
95148
zip_info->offset,
96149
};
97150
char *filename;
98-
int crc = 0, len, comp_size = data_size;
99-
uLongf destlen = data_size + data_size / 500 + 12;
100-
char *compbuffer;
101-
102-
compbuffer = g_malloc(destlen);
103-
crc = crc32(0, NULL, 0);
104-
crc = crc32(crc, (unsigned char *)data, data_size);
105-
lfh.zipmthd = zip_info->compression_level ? 8 : 0;
106-
#ifdef HAVE_ZLIB
107-
if (zip_info->compression_level) {
108-
int error = compress2_int((Byte *)compbuffer, &destlen, (Bytef *)data, data_size, zip_info->compression_level);
109-
if (error == Z_OK) {
110-
if (destlen < data_size) {
111-
data = compbuffer;
112-
comp_size = destlen;
113-
} else
114-
lfh.zipmthd = 0;
115-
} else {
116-
fprintf(stderr, "compress2 returned %d\n", error);
117-
}
118-
}
119-
#endif
120-
lfh.zipcrc = crc;
121-
lfh.zipsize = comp_size;
122-
lfh.zipuncmp = data_size;
123-
cd.zipccrc = crc;
124-
cd.zipcsiz = lfh.zipsize;
125-
cd.zipcunc = data_size;
126-
cd.zipcmthd = lfh.zipmthd;
151+
int len;
152+
153+
lfh.zipmthd = m->method;
154+
lfh.zipcrc = m->crc;
155+
lfh.zipsize = m->data_size;
156+
lfh.zipuncmp = m->uncomp_size;
157+
cd.zipccrc = m->crc;
158+
cd.zipcsiz = m->data_size;
159+
cd.zipcunc = m->uncomp_size;
160+
cd.zipcmthd = m->method;
127161
if (zip_info->zip64) {
128162
cd.zipofst = 0xffffffff;
129163
cd.zipcxtl += sizeof(cd_ext);
@@ -138,8 +172,8 @@ void write_zipmember(struct zip_info *zip_info, char *name, int filelen, char *d
138172
zip_write(zip_info, &lfh, sizeof(lfh));
139173
zip_write(zip_info, filename, filelen);
140174
zip_info->offset += sizeof(lfh) + filelen;
141-
zip_write(zip_info, data, comp_size);
142-
zip_info->offset += comp_size;
175+
zip_write(zip_info, m->data, m->data_size);
176+
zip_info->offset += m->data_size;
143177
dbg_assert(fwrite(&cd, sizeof(cd), 1, zip_info->dir) == 1);
144178
dbg_assert(fwrite(filename, filelen, 1, zip_info->dir) == 1);
145179
zip_info->dir_size += sizeof(cd) + filelen;
@@ -148,7 +182,14 @@ void write_zipmember(struct zip_info *zip_info, char *name, int filelen, char *d
148182
zip_info->dir_size += sizeof(cd_ext);
149183
}
150184

151-
g_free(compbuffer);
185+
g_free(m->buffer);
186+
m->buffer = NULL;
187+
}
188+
189+
void write_zipmember(struct zip_info *zip_info, char *name, int filelen, char *data, int data_size) {
190+
struct zip_member m;
191+
zip_compress_member(zip_info, data, data_size, &m);
192+
write_zipmember_compressed(zip_info, name, filelen, &m);
152193
}
153194

154195
int zip_write_index(struct zip_info *info) {

0 commit comments

Comments
 (0)