本文拟对压缩qcow2镜像所带来的虚拟机性能损失进行简单分析背景生产中发现使用压缩镜像启动的虚拟机开机总是会慢一些。qcow2镜像的压缩方式为qemu-img convert -p -c -O qcow2 zero_disk.qcow2 compress_disk.qcow2分析qemu代码https://download.qemu.org/qemu-9.2.0.tar.xz主要看下使用qcow2后端的guest在读操作时后端操作qcow2文件的一个流程。block/qcow2.c后端driver指定读写回调qcow2_co_preadv_part、qcow2_co_pwritev_partBlockDriver bdrv_qcow2{.format_nameqcow2,.instance_sizesizeof(BDRVQcow2State),.bdrv_probeqcow2_probe,.bdrv_openqcow2_open,.bdrv_closeqcow2_close,.bdrv_reopen_prepareqcow2_reopen_prepare,.bdrv_reopen_commitqcow2_reopen_commit,.bdrv_reopen_commit_postqcow2_reopen_commit_post,.bdrv_reopen_abortqcow2_reopen_abort,.bdrv_join_optionsqcow2_join_options,.bdrv_child_permbdrv_default_perms,.bdrv_co_create_optsqcow2_co_create_opts,.bdrv_co_createqcow2_co_create,.bdrv_has_zero_initqcow2_has_zero_init,.bdrv_co_block_statusqcow2_co_block_status,.bdrv_co_preadv_partqcow2_co_preadv_part,.bdrv_co_pwritev_partqcow2_co_pwritev_part,.bdrv_co_flush_to_osqcow2_co_flush_to_os,.bdrv_co_pwrite_zeroesqcow2_co_pwrite_zeroes,.bdrv_co_pdiscardqcow2_co_pdiscard,.bdrv_co_copy_range_fromqcow2_co_copy_range_from,.bdrv_co_copy_range_toqcow2_co_copy_range_to,.bdrv_co_truncateqcow2_co_truncate,.bdrv_co_pwritev_compressed_partqcow2_co_pwritev_compressed_part,.bdrv_make_emptyqcow2_make_empty,.bdrv_snapshot_createqcow2_snapshot_create,.bdrv_snapshot_gotoqcow2_snapshot_goto,.bdrv_snapshot_deleteqcow2_snapshot_delete,.bdrv_snapshot_listqcow2_snapshot_list,.bdrv_snapshot_load_tmpqcow2_snapshot_load_tmp,.bdrv_measureqcow2_measure,.bdrv_co_get_infoqcow2_co_get_info,.bdrv_get_specific_infoqcow2_get_specific_info,.bdrv_co_save_vmstateqcow2_co_save_vmstate,.bdrv_co_load_vmstateqcow2_co_load_vmstate,.is_formattrue,.supports_backingtrue,.bdrv_co_change_backing_fileqcow2_co_change_backing_file,.bdrv_refresh_limitsqcow2_refresh_limits,.bdrv_co_invalidate_cacheqcow2_co_invalidate_cache,.bdrv_inactivateqcow2_inactivate,.create_optsqcow2_create_opts,.amend_optsqcow2_amend_opts,.strong_runtime_optsqcow2_strong_runtime_opts,.mutable_optsmutable_opts,.bdrv_co_checkqcow2_co_check,.bdrv_amend_optionsqcow2_amend_options,.bdrv_co_amendqcow2_co_amend,.bdrv_detach_aio_contextqcow2_detach_aio_context,.bdrv_attach_aio_contextqcow2_attach_aio_context,.bdrv_supports_persistent_dirty_bitmapqcow2_supports_persistent_dirty_bitmap,.bdrv_co_can_store_new_dirty_bitmapqcow2_co_can_store_new_dirty_bitmap,.bdrv_co_remove_persistent_dirty_bitmapqcow2_co_remove_persistent_dirty_bitmap,};block/qcow2.c读取时走读回调:qcow2_co_preadv_task_entrystaticintcoroutine_fn GRAPH_RDLOCKqcow2_co_preadv_part(BlockDriverState*bs,int64_toffset,int64_tbytes,QEMUIOVector*qiov,size_tqiov_offset,BdrvRequestFlags flags){BDRVQcow2State*sbs-opaque;intret0;unsignedintcur_bytes;/* number of bytes in current iteration */uint64_thost_offset0;QCow2SubclusterType type;AioTaskPool*aioNULL;while(bytes!0aio_task_pool_status(aio)0){/* prepare next request */cur_bytesMIN(bytes,INT_MAX);if(s-crypto){cur_bytesMIN(cur_bytes,QCOW_MAX_CRYPT_CLUSTERS*s-cluster_size);}qemu_co_mutex_lock(s-lock);retqcow2_get_host_offset(bs,offset,cur_bytes,host_offset,type);qemu_co_mutex_unlock(s-lock);if(ret0){gotoout;}if(typeQCOW2_SUBCLUSTER_ZERO_PLAIN||typeQCOW2_SUBCLUSTER_ZERO_ALLOC||(typeQCOW2_SUBCLUSTER_UNALLOCATED_PLAIN!bs-backing)||(typeQCOW2_SUBCLUSTER_UNALLOCATED_ALLOC!bs-backing)){qemu_iovec_memset(qiov,qiov_offset,0,cur_bytes);}else{if(!aiocur_bytes!bytes){aioaio_task_pool_new(QCOW2_MAX_WORKERS);}retqcow2_add_task(bs,aio,qcow2_co_preadv_task_entry,type,host_offset,offset,cur_bytes,qiov,qiov_offset,NULL);if(ret0){gotoout;}}bytes-cur_bytes;offsetcur_bytes;qiov_offsetcur_bytes;}out:if(aio){aio_task_pool_wait_all(aio);if(ret0){retaio_task_pool_status(aio);}g_free(aio);}returnret;}qcow2_co_preadv_task/* * This function can count as GRAPH_RDLOCK because qcow2_co_preadv_part() holds * the graph lock and keeps it until this coroutine has terminated. */staticintcoroutine_fn GRAPH_RDLOCKqcow2_co_preadv_task_entry(AioTask*task){Qcow2AioTask*tcontainer_of(task,Qcow2AioTask,task);assert(!t-l2meta);returnqcow2_co_preadv_task(t-bs,t-subcluster_type,t-host_offset,t-offset,t-bytes,t-qiov,t-qiov_offset);}之后会判断当前读的cluster是什么类型的如果是压缩的镜像会走到QCOW2_SUBCLUSTER_COMPRESSEDstaticintcoroutine_fn GRAPH_RDLOCKqcow2_co_preadv_task(BlockDriverState*bs,QCow2SubclusterType subc_type,uint64_thost_offset,uint64_toffset,uint64_tbytes,QEMUIOVector*qiov,size_tqiov_offset){BDRVQcow2State*sbs-opaque;switch(subc_type){caseQCOW2_SUBCLUSTER_ZERO_PLAIN:caseQCOW2_SUBCLUSTER_ZERO_ALLOC:/* Both zero types are handled in qcow2_co_preadv_part */g_assert_not_reached();caseQCOW2_SUBCLUSTER_UNALLOCATED_PLAIN:caseQCOW2_SUBCLUSTER_UNALLOCATED_ALLOC:assert(bs-backing);/* otherwise handled in qcow2_co_preadv_part */BLKDBG_CO_EVENT(bs-file,BLKDBG_READ_BACKING_AIO);returnbdrv_co_preadv_part(bs-backing,offset,bytes,qiov,qiov_offset,0);caseQCOW2_SUBCLUSTER_COMPRESSED:returnqcow2_co_preadv_compressed(bs,host_offset,offset,bytes,qiov,qiov_offset);caseQCOW2_SUBCLUSTER_NORMAL:if(bs-encrypted){returnqcow2_co_preadv_encrypted(bs,host_offset,offset,bytes,qiov,qiov_offset);}BLKDBG_CO_EVENT(bs-file,BLKDBG_READ_AIO);returnbdrv_co_preadv_part(s-data_file,host_offset,bytes,qiov,qiov_offset,0);default:g_assert_not_reached();}g_assert_not_reached();}qcow2_co_preadv_compressedstaticintcoroutine_fn GRAPH_RDLOCKqcow2_co_preadv_compressed(BlockDriverState*bs,uint64_tl2_entry,uint64_toffset,uint64_tbytes,QEMUIOVector*qiov,size_tqiov_offset){BDRVQcow2State*sbs-opaque;intret0,csize;uint64_tcoffset;uint8_t*buf,*out_buf;intoffset_in_clusteroffset_into_cluster(s,offset);qcow2_parse_compressed_l2_entry(bs,l2_entry,coffset,csize);bufg_try_malloc(csize);if(!buf){return-ENOMEM;}out_bufqemu_blockalign(bs,s-cluster_size);BLKDBG_CO_EVENT(bs-file,BLKDBG_READ_COMPRESSED);retbdrv_co_pread(bs-file,coffset,csize,buf,0);if(ret0){gotofail;}if(qcow2_co_decompress(bs,out_buf,s-cluster_size,buf,csize)0){ret-EIO;gotofail;}qemu_iovec_from_buf(qiov,qiov_offset,out_bufoffset_in_cluster,bytes);fail:qemu_vfree(out_buf);g_free(buf);returnret;}最后调用解压缩/* * qcow2_co_decompress() * * Decompress some data (not more than src_size bytes) to produce exactly * dest_size bytes using the compression method defined by the image * compression type * * dest - destination buffer, dest_size bytes * src - source buffer, src_size bytes * * Returns: 0 on success * a negative error code on failure */ssize_tcoroutine_fnqcow2_co_decompress(BlockDriverState*bs,void*dest,size_tdest_size,constvoid*src,size_tsrc_size){BDRVQcow2State*sbs-opaque;Qcow2CompressFunc fn;switch(s-compression_type){caseQCOW2_COMPRESSION_TYPE_ZLIB:fnqcow2_zlib_decompress;break;#ifdefCONFIG_ZSTDcaseQCOW2_COMPRESSION_TYPE_ZSTD:fnqcow2_zstd_decompress;break;#endifdefault:abort();}returnqcow2_co_do_compress(bs,dest,dest_size,src,src_size,fn);}总结从上面的流程分析可以看出当qcow2被压缩后guest每次需要读取原qcow2文件中的内容时都会让host后端进行一次解压缩这会消耗cpu参考https://github.com/qemu/qemu