qemu with hax to log dma reads & writes jcs.org/2018/11/12/vfio

qcow2: add zstd cluster compression

zstd significantly reduces cluster compression time.
It provides better compression performance maintaining
the same level of the compression ratio in comparison with
zlib, which, at the moment, is the only compression
method available.

The performance test results:
Test compresses and decompresses qemu qcow2 image with just
installed rhel-7.6 guest.
Image cluster size: 64K. Image on disk size: 2.2G

The test was conducted with brd disk to reduce the influence
of disk subsystem to the test results.
The results is given in seconds.

compress cmd:
time ./qemu-img convert -O qcow2 -c -o compression_type=[zlib|zstd]
src.img [zlib|zstd]_compressed.img
decompress cmd
time ./qemu-img convert -O qcow2
[zlib|zstd]_compressed.img uncompressed.img

compression decompression
zlib zstd zlib zstd
------------------------------------------------------------
real 65.5 16.3 (-75 %) 1.9 1.6 (-16 %)
user 65.0 15.8 5.3 2.5
sys 3.3 0.2 2.0 2.0

Both ZLIB and ZSTD gave the same compression ratio: 1.57
compressed image size in both cases: 1.4G

Signed-off-by: Denis Plotnikov <dplotnikov@virtuozzo.com>
QAPI part:
Acked-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20200507082521.29210-4-dplotnikov@virtuozzo.com>
Signed-off-by: Max Reitz <mreitz@redhat.com>

authored by

Denis Plotnikov and committed by
Max Reitz
d298ac10 25dd077d

+180 -2
+169
block/qcow2-threads.c
··· 28 28 #define ZLIB_CONST 29 29 #include <zlib.h> 30 30 31 + #ifdef CONFIG_ZSTD 32 + #include <zstd.h> 33 + #include <zstd_errors.h> 34 + #endif 35 + 31 36 #include "qcow2.h" 32 37 #include "block/thread-pool.h" 33 38 #include "crypto.h" ··· 166 171 return ret; 167 172 } 168 173 174 + #ifdef CONFIG_ZSTD 175 + 176 + /* 177 + * qcow2_zstd_compress() 178 + * 179 + * Compress @src_size bytes of data using zstd compression method 180 + * 181 + * @dest - destination buffer, @dest_size bytes 182 + * @src - source buffer, @src_size bytes 183 + * 184 + * Returns: compressed size on success 185 + * -ENOMEM destination buffer is not enough to store compressed data 186 + * -EIO on any other error 187 + */ 188 + static ssize_t qcow2_zstd_compress(void *dest, size_t dest_size, 189 + const void *src, size_t src_size) 190 + { 191 + ssize_t ret; 192 + size_t zstd_ret; 193 + ZSTD_outBuffer output = { 194 + .dst = dest, 195 + .size = dest_size, 196 + .pos = 0 197 + }; 198 + ZSTD_inBuffer input = { 199 + .src = src, 200 + .size = src_size, 201 + .pos = 0 202 + }; 203 + ZSTD_CCtx *cctx = ZSTD_createCCtx(); 204 + 205 + if (!cctx) { 206 + return -EIO; 207 + } 208 + /* 209 + * Use the zstd streamed interface for symmetry with decompression, 210 + * where streaming is essential since we don't record the exact 211 + * compressed size. 212 + * 213 + * ZSTD_compressStream2() tries to compress everything it could 214 + * with a single call. Although, ZSTD docs says that: 215 + * "You must continue calling ZSTD_compressStream2() with ZSTD_e_end 216 + * until it returns 0, at which point you are free to start a new frame", 217 + * in out tests we saw the only case when it returned with >0 - 218 + * when the output buffer was too small. In that case, 219 + * ZSTD_compressStream2() expects a bigger buffer on the next call. 220 + * We can't provide a bigger buffer because we are limited with dest_size 221 + * which we pass to the ZSTD_compressStream2() at once. 222 + * So, we don't need any loops and just abort the compression when we 223 + * don't get 0 result on the first call. 224 + */ 225 + zstd_ret = ZSTD_compressStream2(cctx, &output, &input, ZSTD_e_end); 226 + 227 + if (zstd_ret) { 228 + if (zstd_ret > output.size - output.pos) { 229 + ret = -ENOMEM; 230 + } else { 231 + ret = -EIO; 232 + } 233 + goto out; 234 + } 235 + 236 + /* make sure that zstd didn't overflow the dest buffer */ 237 + assert(output.pos <= dest_size); 238 + ret = output.pos; 239 + out: 240 + ZSTD_freeCCtx(cctx); 241 + return ret; 242 + } 243 + 244 + /* 245 + * qcow2_zstd_decompress() 246 + * 247 + * Decompress some data (not more than @src_size bytes) to produce exactly 248 + * @dest_size bytes using zstd compression method 249 + * 250 + * @dest - destination buffer, @dest_size bytes 251 + * @src - source buffer, @src_size bytes 252 + * 253 + * Returns: 0 on success 254 + * -EIO on any error 255 + */ 256 + static ssize_t qcow2_zstd_decompress(void *dest, size_t dest_size, 257 + const void *src, size_t src_size) 258 + { 259 + size_t zstd_ret = 0; 260 + ssize_t ret = 0; 261 + ZSTD_outBuffer output = { 262 + .dst = dest, 263 + .size = dest_size, 264 + .pos = 0 265 + }; 266 + ZSTD_inBuffer input = { 267 + .src = src, 268 + .size = src_size, 269 + .pos = 0 270 + }; 271 + ZSTD_DCtx *dctx = ZSTD_createDCtx(); 272 + 273 + if (!dctx) { 274 + return -EIO; 275 + } 276 + 277 + /* 278 + * The compressed stream from the input buffer may consist of more 279 + * than one zstd frame. So we iterate until we get a fully 280 + * uncompressed cluster. 281 + * From zstd docs related to ZSTD_decompressStream: 282 + * "return : 0 when a frame is completely decoded and fully flushed" 283 + * We suppose that this means: each time ZSTD_decompressStream reads 284 + * only ONE full frame and returns 0 if and only if that frame 285 + * is completely decoded and flushed. Only after returning 0, 286 + * ZSTD_decompressStream reads another ONE full frame. 287 + */ 288 + while (output.pos < output.size) { 289 + size_t last_in_pos = input.pos; 290 + size_t last_out_pos = output.pos; 291 + zstd_ret = ZSTD_decompressStream(dctx, &output, &input); 292 + 293 + if (ZSTD_isError(zstd_ret)) { 294 + ret = -EIO; 295 + break; 296 + } 297 + 298 + /* 299 + * The ZSTD manual is vague about what to do if it reads 300 + * the buffer partially, and we don't want to get stuck 301 + * in an infinite loop where ZSTD_decompressStream 302 + * returns > 0 waiting for another input chunk. So, we add 303 + * a check which ensures that the loop makes some progress 304 + * on each step. 305 + */ 306 + if (last_in_pos >= input.pos && 307 + last_out_pos >= output.pos) { 308 + ret = -EIO; 309 + break; 310 + } 311 + } 312 + /* 313 + * Make sure that we have the frame fully flushed here 314 + * if not, we somehow managed to get uncompressed cluster 315 + * greater then the cluster size, possibly because of its 316 + * damage. 317 + */ 318 + if (zstd_ret > 0) { 319 + ret = -EIO; 320 + } 321 + 322 + ZSTD_freeDCtx(dctx); 323 + assert(ret == 0 || ret == -EIO); 324 + return ret; 325 + } 326 + #endif 327 + 169 328 static int qcow2_compress_pool_func(void *opaque) 170 329 { 171 330 Qcow2CompressData *data = opaque; ··· 217 376 fn = qcow2_zlib_compress; 218 377 break; 219 378 379 + #ifdef CONFIG_ZSTD 380 + case QCOW2_COMPRESSION_TYPE_ZSTD: 381 + fn = qcow2_zstd_compress; 382 + break; 383 + #endif 220 384 default: 221 385 abort(); 222 386 } ··· 249 413 fn = qcow2_zlib_decompress; 250 414 break; 251 415 416 + #ifdef CONFIG_ZSTD 417 + case QCOW2_COMPRESSION_TYPE_ZSTD: 418 + fn = qcow2_zstd_decompress; 419 + break; 420 + #endif 252 421 default: 253 422 abort(); 254 423 }
+7
block/qcow2.c
··· 1246 1246 { 1247 1247 switch (s->compression_type) { 1248 1248 case QCOW2_COMPRESSION_TYPE_ZLIB: 1249 + #ifdef CONFIG_ZSTD 1250 + case QCOW2_COMPRESSION_TYPE_ZSTD: 1251 + #endif 1249 1252 break; 1250 1253 1251 1254 default: ··· 3479 3482 } 3480 3483 3481 3484 switch (qcow2_opts->compression_type) { 3485 + #ifdef CONFIG_ZSTD 3486 + case QCOW2_COMPRESSION_TYPE_ZSTD: 3487 + break; 3488 + #endif 3482 3489 default: 3483 3490 error_setg(errp, "Unknown compression type"); 3484 3491 goto out;
+1 -1
configure
··· 1861 1861 lzfse support of lzfse compression library 1862 1862 (for reading lzfse-compressed dmg images) 1863 1863 zstd support for zstd compression library 1864 - (for migration compression) 1864 + (for migration compression and qcow2 cluster compression) 1865 1865 seccomp seccomp support 1866 1866 coroutine-pool coroutine freelist (better performance) 1867 1867 glusterfs GlusterFS backend
+1
docs/interop/qcow2.txt
··· 212 212 213 213 Available compression type values: 214 214 0: zlib <https://www.zlib.net/> 215 + 1: zstd <http://github.com/facebook/zstd> 215 216 216 217 217 218 === Header padding ===
+2 -1
qapi/block-core.json
··· 4293 4293 # Compression type used in qcow2 image file 4294 4294 # 4295 4295 # @zlib: zlib compression, see <http://zlib.net/> 4296 + # @zstd: zstd compression, see <http://github.com/facebook/zstd> 4296 4297 # 4297 4298 # Since: 5.1 4298 4299 ## 4299 4300 { 'enum': 'Qcow2CompressionType', 4300 - 'data': [ 'zlib' ] } 4301 + 'data': [ 'zlib', { 'name': 'zstd', 'if': 'defined(CONFIG_ZSTD)' } ] } 4301 4302 4302 4303 ## 4303 4304 # @BlockdevCreateOptionsQcow2: