使用 fio 测试磁盘 I/O 性能

Linux 使用 fio 测试磁盘 I/O 性能 fio 是一个常见的用于测试磁盘 I/O 性能的工具,支持 19 种不同的 I/O 引擎,包括:sync, mmap, libaio, posixaio, SG v3, splice, null, network, syslet, guasi, solarisaio 等等。fio 一直在更新,最新的版本是 v3.19,它的官网是 fio。 fio 有两种方式对磁盘进行压力测试,一种是命令行指定参数,另外一种是读取配置文件,两者差不太多,但后者可以配合 sh,screen 等方式,保持长期的运行。 安装 $ sudo apt install fio 配置文件参数说明 配置文件属于 ini 格式的,即有区块概念,区块下通过“=”设置键值对。 filename: 指定文件 (设备) 的名称。可以通过冒号分割同时指定多个文件,如 filename=/dev/sda:/dev/sdb。 directory: 设置 filename 的路径前缀。在后面的基准测试中,采用这种方式来指定设备。 name: 指定 job 的名字,在命令行中表示新启动一个 job。 direct: bool 类型,默认为 0, 如果设置成 1,表示不使用 io buffer。 ioengine: I/O 引擎,现在 fio 支持 19 种 ioengine。默认值是 sync 同步阻塞 I/O,libaio 是 Linux 的 native 异步 I/O。关于同步异步,阻塞和非阻塞模型可以参考文章 使用异步 I/O 大大提高应用程序的性能”。 iodepth: 如果 ioengine 采用异步方式,该参数表示一批提交保持的 io 单元数。该参数可参考文章“Fio 压测工具和 io 队列深度理解和误区”。 rw: I/O 模式,随机读写,顺序读写等等。可选值:read,write,randread,randwrite,rw,randrw。 bs: I/O block 大小,默认是 4k。测试顺序读写时可以调大。 size: 指定 job 处理的文件的大小。 numjobs: 指定 job 的克隆数(线程)。 time_based: 如果在 runtime 指定的时间还没到时文件就被读写完成,将继续重复知道 runtime 时间结束。 runtime: 指定在多少秒后停止进程。如果未指定该参数,fio 将执行至指定的文件读写完全完成。 group_reporting: 当同时指定了 numjobs 了时,输出结果按组显示。 命令行使用 # 顺序读 $ fio -filename=/dev/sda -direct=1 -iodepth 1 -thread -rw=read -ioengine=psync -bs=16k -size=200G -numjobs=30 -runtime=1000 -group_reporting -name=mytest # 顺序写 $ fio -filename=/dev/sda -direct=1 -iodepth 1 -thread -rw=write -ioengine=psync -bs=16k -size=200G -numjobs=30 -runtime=1000 -group_reporting -name=mytest # 随机读 $ fio -filename=/dev/sda -direct=1 -iodepth 1 -thread -rw=randread -ioengine=psync -bs=16k -size=200G -numjobs=30 -runtime=1000 -group_reporting -name=mytest # 随机写 $ fio -filename=/dev/sda -direct=1 -iodepth 1 -thread -rw=randwrite -ioengine=psync -bs=16k -size=200G -numjobs=30 -runtime=1000 -group_reporting -name=mytest # 混合随机读写 $ fio -filename=/dev/sda -direct=1 -iodepth 1 -thread -rw=randrw -rwmixread=70 -ioengine=psync -bs=16k -size=200G -numjobs=30 -runtime=100 -group_reporting -name=mytest -ioscheduler=noop 配置文件启动 # fio.conf [global] ioengine=libaio iodepth=128 direct=0 thread=1 numjobs=16 norandommap=1 randrepeat=0 runtime=60 ramp_time=6 size=1g directory=/your/path [read4k-rand] stonewall group_reporting bs=4k rw=randread [read64k-seq] stonewall group_reporting bs=64k rw=read [write4k-rand] stonewall group_reporting bs=4k rw=randwrite [write64k-seq] stonewall group_reporting bs=64k rw=write $ fio fio.conf read4k-rand: (groupid=0, jobs=16): err= 0: pid=2571: Tue May 12 15:28:36 2020 read: IOPS=33.4k, BW=131MiB/s (137MB/s)(7834MiB/60002msec) slat (nsec): min=1703, max=3754.6k, avg=476047.81, stdev=421903.44 clat (usec): min=4, max=93701, avg=60830.41, stdev=9669.21 lat (usec): min=171, max=94062, avg=61307.14, stdev=9735.47 clat percentiles (usec): | 1.00th=[41681], 5.00th=[45876], 10.00th=[48497], 20.00th=[51643], | 30.00th=[54789], 40.00th=[57410], 50.00th=[60556], 60.00th=[63701], | 70.00th=[66847], 80.00th=[69731], 90.00th=[73925], 95.00th=[77071], | 99.00th=[81265], 99.50th=[82314], 99.90th=[85459], 99.95th=[86508], | 99.99th=[88605] bw ( KiB/s): min= 6240, max=11219, per=6.27%, avg=8377.40, stdev=1197.43, samples=1920 iops : min= 1560, max= 2804, avg=2094.01, stdev=299.31, samples=1920 lat (usec) : 10=0.01%, 250=0.01%, 500=0.01% lat (msec) : 2=0.01%, 4=0.01%, 10=0.01%, 20=0.02%, 50=14.27% lat (msec) : 100=85.80% cpu : usr=0.59%, sys=1.94%, ctx=1545619, majf=0, minf=0 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=107.7% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% issued rwt: total=2003580,0,0, short=0,0,0, dropped=0,0,0 latency : target=0, window=0, percentile=100.00%, depth=128 read64k-seq: (groupid=1, jobs=16): err= 0: pid=2590: Tue May 12 15:28:36 2020 read: IOPS=10.7k, BW=674MiB/s (707MB/s)(12.3GiB/18607msec) slat (usec): min=8, max=12592, avg=1495.75, stdev=3792.74 clat (usec): min=2, max=193705, avg=189323.53, stdev=12000.95 lat (usec): min=21, max=193723, avg=190819.12, stdev=11397.67 clat percentiles (msec): | 1.00th=[ 180], 5.00th=[ 180], 10.00th=[ 182], 20.00th=[ 190], | 30.00th=[ 192], 40.00th=[ 192], 50.00th=[ 192], 60.00th=[ 192], | 70.00th=[ 192], 80.00th=[ 192], 90.00th=[ 192], 95.00th=[ 192], | 99.00th=[ 194], 99.50th=[ 194], 99.90th=[ 194], 99.95th=[ 194], | 99.99th=[ 194] bw ( KiB/s): min=41000, max=43682, per=6.12%, avg=42274.07, stdev=551.21, samples=592 iops : min= 640, max= 682, avg=660.07, stdev= 8.64, samples=592 lat (usec) : 4=0.01%, 50=0.01%, 100=0.01%, 250=0.02%, 500=0.01% lat (usec) : 750=0.01% lat (msec) : 10=0.01%, 20=0.05%, 50=0.19%, 100=0.26%, 250=100.46% cpu : usr=0.13%, sys=2.13%, ctx=49722, majf=0, minf=16 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.3%, >=64=131.4% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% issued rwt: total=198676,0,0, short=0,0,0, dropped=0,0,0 latency : target=0, window=0, percentile=100.00%, depth=128 write4k-rand: (groupid=2, jobs=16): err= 0: pid=2607: Tue May 12 15:28:36 2020 write: IOPS=2409k, BW=9411MiB/s (9868MB/s)(16.0GiB/1741msec) clat percentiles (nsec): | 1.00th=[ 0], 5.00th=[ 0], 10.00th=[ 0], 20.00th=[ 0], | 30.00th=[ 0], 40.00th=[ 0], 50.00th=[ 0], 60.00th=[ 0], | 70.00th=[ 0], 80.00th=[ 0], 90.00th=[ 0], 95.00th=[ 0], | 99.00th=[ 0], 99.50th=[ 0], 99.90th=[ 0], 99.95th=[ 0], | 99.99th=[ 0] cpu : usr=14.12%, sys=85.68%, ctx=2337, majf=0, minf=16 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% issued rwt: total=0,4194304,0, short=0,0,0, dropped=0,0,0 latency : target=0, window=0, percentile=100.00%, depth=128 write64k-seq: (groupid=3, jobs=16): err= 0: pid=2623: Tue May 12 15:28:36 2020 write: IOPS=221k, BW=13.5GiB/s (14.5GB/s)(16.0GiB/1188msec) clat percentiles (nsec): | 1.00th=[ 0], 5.00th=[ 0], 10.00th=[ 0], 20.00th=[ 0], | 30.00th=[ 0], 40.00th=[ 0], 50.00th=[ 0], 60.00th=[ 0], | 70.00th=[ 0], 80.00th=[ 0], 90.00th=[ 0], 95.00th=[ 0], | 99.00th=[ 0], 99.50th=[ 0], 99.90th=[ 0], 99.95th=[ 0], | 99.99th=[ 0] cpu : usr=3.63%, sys=96.11%, ctx=1862, majf=0, minf=16 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.2%, >=64=99.6% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% issued rwt: total=0,262144,0, short=0,0,0, dropped=0,0,0 latency : target=0, window=0, percentile=100.00%, depth=128 Run status group 0 (all jobs): READ: bw=131MiB/s (137MB/s), 131MiB/s-131MiB/s (137MB/s-137MB/s), io=7834MiB (8215MB), run=60002-60002msec Run status group 1 (all jobs): READ: bw=674MiB/s (707MB/s), 674MiB/s-674MiB/s (707MB/s-707MB/s), io=12.3GiB (13.2GB), run=18607-18607msec Run status group 2 (all jobs): WRITE: bw=9411MiB/s (9868MB/s), 9411MiB/s-9411MiB/s (9868MB/s-9868MB/s), io=16.0GiB (17.2GB), run=1741-1741msec Run status group 3 (all jobs): WRITE: bw=13.5GiB/s (14.5GB/s), 13.5GiB/s-13.5GiB/s (14.5GB/s-14.5GB/s), io=16.0GiB (17.2GB), run=1188-1188msec Disk stats (read/write): nvme0n1: ios=1819561/61, merge=0/580, ticks=2577676/16, in_queue=2501764, util=96.22% 从上述结果的 bw 和 iops 来看,这是块走 pcie 3.0 * 2 的 ssd,大概率是 m.2 接口的 SSD。 ...

修复丢失的 GPG 密钥 Apt 存储库错误(NO_PUBKEY)

Linux Ubuntu 修复丢失的 GPG 密钥 Apt 存储库错误(NO_PUBKEY) apt遇到gpg error:pub key not found问题。 报错一般出现在 apt/apt-get update/upgrade 相关操作时。大致报错内容为: W: GPG error: https://packages.grafana.com/oss/deb stable InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 8C8C34C524098CB6 注意这个 8C8C34C524098CB6 这个就是 pub key。 这个问题出现的原因是:当您添加存储库,而忘记添加其公共密钥时,或者在尝试导入 GPG 密钥时可能出现临时密钥服务器错误 它造成的影响是:无法更新软件索引,从而无法更新软件 解决方案:导入公共GPG密钥 修复单个存储库的丢失 GPG 密钥问题 sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net:80 --recv-keys THE_MISSING_KEY_HERE THE_MISSING_KEY_HERE 在这里指代上面的 8C8C34C524098CB6。 完整指令是: sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net:80 --recv-keys 8C8C34C524098CB6 批量修复存储库的丢失 GPG 密钥问题 sudo apt update 2>&1 1>/dev/null | sed -ne 's/.*NO_PUBKEY //p' | while read key; do if ! [[ ${keys[*]} =~ "$key" ]]; then sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net:80 --recv-keys "$key"; keys+=("$key"); fi; done 参考 Fix Missing GPG Key Apt Repository Errors (NO_PUBKEY)

修改已有用户 uid 以及 gid

Linux 修改已有用户 uid 以及 gid 主要是为了解决 nfs 权限问题,所以一些特定的机器需要对特定的用户配置特定的uid以及gid。 配置前必须保证两点: 需要变成的id是否被占用: id $your_id 需要变动的用户是否有进程: ps -au $username 如果出现了上述两种情况,根据实际情况处理,一般的处理方法为: id被占用:删掉占用的用户,或者占用的用户重新分配id 有进程存在:ps -aux $username|grep -v PID|awk '{print $1}'|xargs kill -9 上述情况判断完成后,就需要重新指定id了。 # 假设用户tomcat的uid为90 $ id tomcat uid=90(tomcat) gid=90(tomcat) groups=90(tomcat) $ usermod -u 91 tomcat $ groupmod -g 91 tomcat $ id tomcat uid=91(tomcat) gid=91(tomcat) groups=91(tomcat) find / -user 90 -exec chown -h tomcat {} ; find / -group 90-exec chgrp -h tomcat {} ; 参考 Linux 下 NFS 服务权限问题解决 Permission denied

关于磁盘及 fs 的几个问题处理

Linux 关于磁盘及 fs 的几个问题处理 有块磁盘出现了错误,其挂载的分区可以 cd 进入,但无法通过 ls 列出当前目录文件,显示错误为: cannot list ......:Bad message 没遇到这个问题,没办法,可能与之前的 dd 操作有关? 查到的资料显示可能是 inode 损坏,那么就尝试清理 inode。 First list bad file with inode e.g. $ ls –il Output 14071947 -rw-r--r-- 1 dba 0 2010-01-27 15:49 -®Å Note: 14071947 is inode number. Now Use find command to delete file by inode: $ find . -inum 14071947 -exec rm -f {} ; It will find that bad file and will remove it with force i.e remove without prompt. 但并不奏效,因为这个目录都无法再列出文件,而不是这个问题提出者遇到的无法 rm 的问题。 ...

内存文件系统使用

Linux Ubuntu 内存文件系统使用 内存的速度足够快,那么在内存中开辟一个存储空间,挂在到特定分区,实现快速缓存的方案。 tmpfs 是一种虚拟内存文件系统, 它存储在 VM(virtual memory) 里面, VM 是由 Linux 内核里面的 VM 子系统管理,现在大多数操作系统都采用了虚拟内存(MMU)管理机制。 $ mount -t tmpfs -o size= 1024m tmpfs /mnt 优点 大小随意分配 大小根据实际存储的容量而变化 不指定size大小是物理内存的一半 读写速度超级快 缺点 断电内容消失 echo "tmpfs /mnt tmpfs size=1024m 0 0\n" >> /etc/fstab 既然做出了文件系统,就来测个速度吧,fio 配置文件如下。 [global] ioengine=libaio direct=0 thread=1 norandommap=1 randrepeat=0 runtime=60 ramp_time=6 size=1g directory=/path numjobs=16 iodepth=128 [read4k-rand] stonewall group_reporting bs=4k rw=randread [read64k-seq] stonewall group_reporting bs=64k rw=read [write4k-rand] stonewall group_reporting bs=4k rw=randwrite [write64k-seq] stonewall group_reporting bs=64k rw=write Jobs: 16 (f=0): [_(48),/(16)][-.-%][r=0KiB/s,w=13.0GiB/s][r=0,w=3411k IOPS][eta 01m:05s] read4k-rand: (groupid=0, jobs=16): err= 0: pid=13736: Mon Jun 8 15:25:25 2020 read: IOPS=3534k, BW=13.5GiB/s (14.5GB/s)(16.0GiB/1187msec) clat percentiles (nsec): | 1.00th=[ 0], 5.00th=[ 0], 10.00th=[ 0], 20.00th=[ 0], | 30.00th=[ 0], 40.00th=[ 0], 50.00th=[ 0], 60.00th=[ 0], | 70.00th=[ 0], 80.00th=[ 0], 90.00th=[ 0], 95.00th=[ 0], | 99.00th=[ 0], 99.50th=[ 0], 99.90th=[ 0], 99.95th=[ 0], | 99.99th=[ 0] cpu : usr=14.95%, sys=84.75%, ctx=1711, majf=0, minf=2049 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% issued rwt: total=4194304,0,0, short=0,0,0, dropped=0,0,0 latency : target=0, window=0, percentile=100.00%, depth=128 read64k-seq: (groupid=1, jobs=16): err= 0: pid=13752: Mon Jun 8 15:25:25 2020 read: IOPS=267k, BW=16.3GiB/s (17.5GB/s)(16.0GiB/981msec) clat percentiles (nsec): | 1.00th=[ 0], 5.00th=[ 0], 10.00th=[ 0], 20.00th=[ 0], | 30.00th=[ 0], 40.00th=[ 0], 50.00th=[ 0], 60.00th=[ 0], | 70.00th=[ 0], 80.00th=[ 0], 90.00th=[ 0], 95.00th=[ 0], | 99.00th=[ 0], 99.50th=[ 0], 99.90th=[ 0], 99.95th=[ 0], | 99.99th=[ 0] cpu : usr=1.14%, sys=98.40%, ctx=1344, majf=0, minf=32784 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.2%, >=64=99.6% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% issued rwt: total=262144,0,0, short=0,0,0, dropped=0,0,0 latency : target=0, window=0, percentile=100.00%, depth=128 write4k-rand: (groupid=2, jobs=16): err= 0: pid=13768: Mon Jun 8 15:25:25 2020 write: IOPS=1572k, BW=6141MiB/s (6439MB/s)(16.0GiB/2668msec) clat percentiles (nsec): | 1.00th=[ 0], 5.00th=[ 0], 10.00th=[ 0], 20.00th=[ 0], | 30.00th=[ 0], 40.00th=[ 0], 50.00th=[ 0], 60.00th=[ 0], | 70.00th=[ 0], 80.00th=[ 0], 90.00th=[ 0], 95.00th=[ 0], | 99.00th=[ 0], 99.50th=[ 0], 99.90th=[ 0], 99.95th=[ 0], | 99.99th=[ 0] cpu : usr=11.57%, sys=88.18%, ctx=3745, majf=0, minf=2424 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% issued rwt: total=0,4194304,0, short=0,0,0, dropped=0,0,0 latency : target=0, window=0, percentile=100.00%, depth=128 write64k-seq: (groupid=3, jobs=16): err= 0: pid=13784: Mon Jun 8 15:25:25 2020 write: IOPS=254k, BW=15.5GiB/s (16.6GB/s)(16.0GiB/1033msec) clat percentiles (nsec): | 1.00th=[ 0], 5.00th=[ 0], 10.00th=[ 0], 20.00th=[ 0], | 30.00th=[ 0], 40.00th=[ 0], 50.00th=[ 0], 60.00th=[ 0], | 70.00th=[ 0], 80.00th=[ 0], 90.00th=[ 0], 95.00th=[ 0], | 99.00th=[ 0], 99.50th=[ 0], 99.90th=[ 0], 99.95th=[ 0], | 99.99th=[ 0] cpu : usr=14.67%, sys=84.97%, ctx=1430, majf=0, minf=16 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.2%, >=64=99.6% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1% issued rwt: total=0,262144,0, short=0,0,0, dropped=0,0,0 latency : target=0, window=0, percentile=100.00%, depth=128 Run status group 0 (all jobs): READ: bw=13.5GiB/s (14.5GB/s), 13.5GiB/s-13.5GiB/s (14.5GB/s-14.5GB/s), io=16.0GiB (17.2GB), run=1187-1187msec Run status group 1 (all jobs): READ: bw=16.3GiB/s (17.5GB/s), 16.3GiB/s-16.3GiB/s (17.5GB/s-17.5GB/s), io=16.0GiB (17.2GB), run=981-981msec Run status group 2 (all jobs): WRITE: bw=6141MiB/s (6439MB/s), 6141MiB/s-6141MiB/s (6439MB/s-6439MB/s), io=16.0GiB (17.2GB), run=2668-2668msec Run status group 3 (all jobs): WRITE: bw=15.5GiB/s (16.6GB/s), 15.5GiB/s-15.5GiB/s (16.6GB/s-16.6GB/s), io=16.0GiB (17.2GB), run=1033-1033msec io带宽很高,64k 随机读的 IOPS 居然达到了惊人的 1572k。

初学C语言,几种利用嵌套循环的字母打印格式

[[C]] 初学C语言,几种利用嵌套循环的字母打印格式 1、最简单的换行嵌套,一个 for 循环控制行数,一个 for 循环控制每行输出量。 #include <stdio.h> /*输出类似于 $ $$ $$$ $$$$ $$$$$ */ int main(void) { int m,n; for(m=1;m<=5;m++) for(n=1;n<=m;n++) { printf("$"); if(n==m) printf("\n"); } } 2、相比于第一例,多了一个字符倒序输出,利用数组下标中加上变量 n 实现。 #include <stdio.h> /*输出形如 F FE FED FEDC FEDCB FEDCBA */ int main(void) { char lett[27]="ABCDEFGHIJKLMNOPQRSTUVWXYZ"; int m,n; for(m=1;m<=6;m++) for(n=1;n<=m;n++) { printf("%c",lett[6-n]); if(n==m) printf("\n"); } } 3、这个的难点在于一开始不理解 ch++ 的正确使用方法,直接使用数组的方式两个循环嵌套去做,(错误)代码如下: #include <stdio.h> int main(void) { char lett[27]="ABCDEFGHIJKLMNOPQRSTUVWXYZ"; int m,n; for(m=0;m<=6;m++) for(n=0;n<=m;n++) { printf("%c",lett[m+n]); if(n==m) printf("\n"); } } 先看这个的输出结果: ...

单元测试浅析

单元测试浅析 1、何为单元测试 在计算机编程中,单元测试(英语:[[Unit Testing]])又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。 2、单元测试的优势 - 适应变更 单元测试允许程序员在未来重构代码,并且确保模块依然工作正确(复合测试)。这个过程就是为所有函数和方法编写单元测试,一旦变更导致错误发生,借助于单元测试可以快速定位并修复错误。 可读性强的单元测试可以使程序员方便地检查代码片断是否依然正常工作。良好设计的单元测试案例覆盖程序单元分支和循环条件的所有路径。 - 简化集成 单元测试消除程序单元的不可靠,采用自底向上的测试路径。通过先测试程序部件再测试部件组装,使集成测试变得更加简单。 - 文档记录 单元测试提供了系统的一种文档记录。借助于查看单元测试提供的功能和单元测试中如何使用程序单元,开发人员可以直观的理解程序单元的基础 API。 单元测试具体表现了程序单元成功的关键特点。这些特点可以指出正确使用和非正确使用程序单元,也能指出需要捕获的程序单元的负面表现(译注:异常和错误)。尽管很多软件开发环境不仅依赖于代码做为产品文档,在单元测试中和单元测试本身确实文档化了程序单元的上述关键特点。 - 表达设计 在测试驱动开发的软件实践中,单元测试可以取代正式的设计。每一个单元测试案例均可以视为一项类、方法和待观察行为等设计元素。 3、单元测试的局限 测试不可能发现所有的程序错误,单元测试也不例外。按定义,单元测试只测试程序单元自身的功能。因此,它不能发现集成错误、性能问题、或者其他系统级别的问题。单元测试结合其他软件测试活动更为有效。与其它形式的软件测试类似,单元测试只能表明测到的问题,不能表明不存在未测试到的错误。 4、单元测试的要求 4.1 代码可测性 这种情况可能是你代码本身导致的,首先你要写具有“可测性”的代码,这意味着你不能写面向过程的,流水式的,几百行逻辑堆一起的代码(也叫意大利面代码,就像一盘意大利面一样搅在一起的代码。),你要学一些模块化技巧,面向对象和函数式编程理念,还有很多其它具体方法,比如能用本地变量,就不要用全局变量等等,让你的代码具有可测性,这些知识的学习应该放在单元测试之前。 4.2 测试独立性 每个理想的测试案例独立于其它案例;为测试时隔离模块,经常使用 stubs、mock 或 fake 等测试马甲程序。不与外部(包括文件、数据库、网络、外部服务)直接相依。 4.3 测试有效性 测试代码和功能代码同时提交。 4.4 测试及时性 单元测试的目的是,针对修改的每个函数或方法,进行快速的测试,观察结果是否如预期。不说代码改动的效果如何(性能,规范等),至少可以正常运行。 4.5 一个测试案例只测一个方法 单元测试注重快速,小而美,针对一个实例方法,可能会有很多不同方向的测试,将这些测试方法分开。假设测试账户有效性,账户有效写个方法,账户无效写个方法。 5、单元测试之stub,mock,fake 这三个概念来源自面向对象设计语言在 TDD 方法下的实践。 stub 检测回调值return。我们用 pytest 或者 unittest 替代。 mock 模拟和外界的连接。我们用 python mock 替代。 fake 虚拟环境。我们可以显式修改临时变量。 6、python 常见单元测试框架 6.1 unittest python 标准库自带的单元测试框架。 6.2 pytest 非常简洁好用的单元测试框架。简单使用方法可以参加博客pytest的简单学习 6.3 mock 单元测试应该只针对当前单元进行测试, 所有的内部或外部的依赖应该是稳定的, 已经在别处进行测试过的。 ...

单链表的Python实现(列表实现)

Python Data Structure 单链表的 Python 实现 一、节点 节点,即 C 语言里使用 struct 语句定义的一种非基础数据类型,在 Python 中,定义一个class 类。 class Node(object): def __init__(self, data, next=None): """包含一个数据域,一个 next 域的节点,next 是对下一个数据的引用 """ self.data = data self.next = next class TwoWayNode(Node): """继承 Node 类的双指针节点类,新包含一个 previous 的引用 """ def __init__(self, data, previous=None, next=None): Node.__init__(self, data, next) self.previous = previous def main(): pass if __name__ == "__main__": main() 二、单链表 单链表,指的是由多个节点形成的链式结构,一个节点包括一个数据域及一个 next 域。 除头结点外,每个节点都有且仅有一个前驱;除尾节点外,每个节点有且仅有一个后继。 ADT 思想(忽略)。 定义一个单链表类,依次给出以下的方法: 1、 在首部插入节点 2、在尾部插入节点 3、在任意位置插入节点 4、从首部删除节点 5、从尾部删除节点 6、从任意位置删除节点 7、遍历输出 8、根据数值查找单链表中是否存在项,返回 True or False 及索引位置 9、根据给出的索引替换掉相应位置的值 from node import Node class SingleList(object): """以节点的方式实现链表,默认实现空列表 """ def __init__(self): self.head = None # 在开始处插入 def add_node_first(self, data): self.head = Node(data, self.head) # 在末尾插入,若为空列表则直接插入,否则遍历到尾部 def add_node_last(self, data): new_node = Node(data) if self.head is None: self.head = new_node else: probe = self.head while probe.next is not None: probe = probe.next probe.next = new_node # 任意位置添加节点,若 index 小于零,加在首部,若 index 大于链表长度,加在尾部 def add_node_anywhere(self, index, data): if self.head is None or index <= 0: self.head = Node(data, self.head) else: probe = self.head while index >1 and probe.next is not None: probe = probe.next index -= 1 probe.next = Node(data, probe.next) # 从首部删除节点 def pop_node_first(self): if self.head is not None: removed_item = self.head.data self.head = self.head.next return removed_item else: return -1 # 从尾部删除节点 def pop_node_last(self): if self.head is None: return -1 elif self.head.next is None: removed_item = self.head.data self.head = None else: probe = self.head while probe.next.next is not None: probe = probe.next removed_item = probe.next.data probe.next = None return removed_item # 任意位置删除节点,若 index 小于零,则删除首部节点,若 index 大于链表长度,则删除尾部节点 def pop_node_anywhere(self, index): if index <= 0 or self.head.next is None: removed_item = self.head.data self.head = self.head.next return removed_item else: probe = self.head while index > 1 and probe.next.next is not None: probe = probe.next index -= 1 removed_item = probe.next.data probe.next = probe.next.next return removed_item # 遍历输出 def traverse(self): probe = self.head while probe is not None: print(probe.data) probe = probe.next # 根据数值查找链表有没有该数据 def search(self, data): probe = self.head cnt = 0 while probe is not None and data != probe.data: probe = probe.next cnt += 1 if probe is not None: return "In", cnt else: return "Not in", -1 # 根据索引位置替换数据 def replace(self, index, new_data): probe = self.head while index > 0 and probe is not None: probe = probe.next index -= 1 if probe is None: return -1 else: probe.data = new_data return "Done" # 测试用例 a = SingleList() a.add_node_first(1) a.add_node_first(2) a.add_node_first(3) print(a.search(2)) a.add_node_first(4) a.add_node_last(5) a.add_node_first(6) a.add_node_anywhere(100,33) a.pop_node_anywhere(2) a.traverse() print("--------")

常见排序算法的 Python 实现

Python [[Algorithm]] 常见排序算法的 Python 实现 选择排序 """ 选择排序,顾名思义,扫描全表,选最小值 外循环走size-1次,每一次确定当前一个最小的数 内循环走size-(已经确定的最小数),理解为当前位置之前的数字都已有序,从当前位置出发到结尾扫描确定当前最小的数字 时间复杂度:平均O(n**2),最坏O(n**2),最好O(n**2) 空间复杂度:O(1) 稳定性:不稳定 """ def select_sort(sort_list): for i in range(size-1): min = i for j in range(i, size): if sort_list[j] < sort_list[min]: min = j sort_list[min], sort_list[i] = sort_list[i], sort_list[min] print("选择排序结果是:",sort_list) 冒泡排序 """ 冒泡排序,逐个比较,将最大的数排到最后方 外循环走size-1次,每次确定一个最大的数 内循环走size-(当前已确定的数),理解为从头开始,两两比较,a(n)>a(n+1),则交换 时间复杂度:平均O(n**2),最坏O(n**2),最好O(n) 空间复杂度:O(1) 稳定性:稳定 """ def bub_sort(sort_list): for i in range(size-1): for j in range(1, size): if sort_list[j] < sort_list[j-1]: sort_list[j], sort_list[j-1] = sort_list[j-1], sort_list[j] print("冒泡排序结果是:",sort_list) 插入排序 """ 插入排序,类似于体育课排队列 外循环走size-1次,每次确定一个较小的数,一次内循环结束,当前位置的左侧是相对大小确定的 内循环走0次或者当前已确定数的次数,理解为当前数与之前的第一个数对比,若小于则交换,继而继续比较,所以最少0次,最多当前已确定数次 时间复杂度:平均O(n**2),最坏O(n**2),最好O(n) 空间复杂度:O(1) 稳定性:稳定 """ def insert_sort(sort_list): for i in range(1, size): j = i while j > 0 and sort_list[j]<sort_list[j-1]: sort_list[j], sort_list[j-1] = sort_list[j-1], sort_list[j] j -= 1 print("插入排序结果是:",sort_list) 希尔排序 """ 希尔排序(该处指配合插入排序的希尔排序),由插入排序的定义可以看出来,当前的数想要确定位置必须与之前的数字逐个比较, 而希尔排序改成h个比较,这样做的好处是,针对数据量大的数组,排序的过程更轻松(构建h个不同的子数组,每个子数组逻辑相邻(相差距离为h)) 外循环的运算次数为(size = size//3循环,直到size等于1,每循环一次,运算次数加一 如size = 150,150//3=50(1次),50//3=16(2次),16//3=5(3次),5//3=1(4次)) 内循环为选择插入排序,次数由当前的外循环变量决定 时间复杂度:平均O(n**1.3),其他情况不好分析 空间复杂度:O(1) 稳定性:不稳定 """ def shell_sort(sort_list): h = 1 while h < size//3: h = h*3+1 while h >=1: for i in range(h, size): j = i while j > h and sort_list[j]<sort_list[j-h]: sort_list[j], sort_list[j-h] = sort_list[j-h], sort_list[j] j -= h h = h//3 print("希尔排序结果是:",sort_list) 归并排序 """ 归并排序,分治算法思想,将一个大问题分解成若干个小问题,若问题类似可用递归完成 常见两种归并算法,自顶向下和自底向上 自顶向下的算法用递归的方法,先解决左边的排序,再解决右边的排序 自底向上的算法用拆解合并的思想,先拆成size/2个小数组进行归并排序,继而将结果拆成size/4个数组归并排序,当size/(2**n)<1时完成排序 时间复杂度:平均O(nlog2n),最坏O(nlog2n),最好O(nlog2n) 空间复杂度:O(n)(需要一个临时数组来保存) 稳定性:稳定 """ class merge(object): #原地归并抽象方法,方便复用,传入数组,左值,中值,右值 def merge_sort(self, sort_list, lo, mid, hi): i = lo j = mid+1 aux = copy.deepcopy(sort_list) for k in range(lo, hi+1): if i > mid: sort_list[k] = aux[j] j += 1 elif j > hi: sort_list[k] = aux[i] i += 1 elif aux[j] <= aux[i]: sort_list[k] = aux[j] j += 1 else: sort_list[k] = aux[i] i += 1 def sort(self, sort_list): self.sort1(sort_list, 0, size-1) #自顶向下的归并排序 def sort1(self, sort_list, lo, hi): if hi <= lo: return sort_list mid = lo + (hi-lo)//2 self.sort1(sort_list, lo, mid) self.sort1(sort_list, mid+1, hi) self.merge_sort(sort_list, lo, mid, hi) def sort2(self, sort_list): sz = 1 while sz < size: lo = 0 while lo < size-sz: self.merge_sort(sort_list, lo, lo+sz-1, min(lo+sz+sz-1, size-1)) lo += sz+sz sz = sz+sz print(sort_list) 快速排序 """ 快速排序,是常规条件下最快的排序算法,使用分治算法思想,利用递归完成 首先先改变数组内部顺序(消除输入依赖),然后通过切分函数找出一个值(二分切分中,该值越接近正确顺序的中值越好) 以该值为mid,递归调用自身,分而治之 重点在于切分函数,二分切分函数的思想是,以某子数组第一个数a为基准, 从左往右扫描找出一个大于a的数,再从右往左扫描找出一个小于a的数,两者交换 最后将a放到正确的位置,返回切分的数的索引 时间复杂度:平均O(nlog2n),最坏O(n**2),最好O(nlog2n) 空间复杂度:O(log2n)(需要一个临时数组来保存) 稳定性:不稳定 """ class quick(object): #消除输入依赖 def sort(self, sort_list): random.sample(sort_list, size) self.sort1(sort_list, 0, size-1) #递归主函数体,从切分函数得到切分索引,左右递归,递归结束不用归并 def sort1(self, sort_list, lo, hi): if hi <= lo: return sort_list j = self.partition(sort_list, lo, hi) self.sort1(sort_list, lo, j-1) self.sort1(sort_list, j+1, hi) #切分函数,左右指针,轮流扫描,交换位置,最后将切分元素放到正确的位置,返回切分索引 def partition(self, sort_list, lo, hi): i = lo j = hi+1 v = sort_list[lo] while True: i = i + 1 while sort_list[i]<v: if i==hi: break i += 1 j = j - 1 while v < sort_list[j]: if j==lo: break j -= 1 if i >= j: break sort_list[i], sort_list[j] = sort_list[j], sort_list[i] sort_list[lo], sort_list[j] = sort_list[j], sort_list[lo] return j 基数排序 """ 基数排序,不进行比较的整数排序算法,基数指的是整数的进制(默认为10), 根据位数要做几次不同的桶排序,位数的计算为int(math.ceil(math.log(max(sort_list)+1, radix))) 每次循环完成当前位数(个位、十位、百位)的大小排序,理解过程可见http://bubkoo.com/2014/01/15/sort-algorithm/radix-sort/ 一共有十个桶,分别对应0-10,每个桶有若干数据,则桶可以用二维数组完成,记为a[index1][index2], 对每一个sort_list里的数,index1 = sort_list_num%(radix**i)//(radix**(i-1)) 时间复杂度:平均O(k*n),最坏O(k*n),最好O(k*n),k为最大数字的位数 空间复杂度:O(n) 稳定性:稳定 """ def radix_sort(sort_list, radix=10): """sort_list为整数列表, radix为基数""" K = int(math.ceil(math.log(max(sort_list)+1, radix))) for i in range(1, K+1): bucket = [[] for i in range(radix)] for val in sort_list: bucket[val%(radix**i)//(radix**(i-1))].append(val) del sort_list[:] for each in bucket: sort_list.extend(each) print(sort_list) 测试 import copy import random import math sort_list = [20,1,24,54,11,26,87,45,32,544,25,87,47,48,58,1024] global size size = len(sort_list) #select_sort(sort_list) #bub_sort(sort_list) #insert_sort(sort_list) #shell_sort(sort_list) #自顶向下归并排序测试 # a = merge() # a.sort(sort_list) # print(sort_list) #自底向上归并排序测试 # a = merge() # a.sort2(sort_list) # print(sort_list) #快速排序测试 # print(sort_list) # a = quick() # a.sort(sort_list) # print(sort_list) #基数排序测试 #radix_sort(sort_list)

新冠肺炎记忆

杂谈 新冠肺炎记忆 这次新冠肺炎的出现让我想起了 03 年,那年才刚上小学,没有什么记忆,只是学还是要上,经常检测体温,家里买了很多白醋等记忆。 时间来到 2020 年,武汉爆发了新型冠状病毒肺炎,我大概 1 月 16 号得知这个情况,当时也没在意,20 号从杭州回到了老家,这一路没什么感受,一切都和往常一样。时间来到 23 号,武汉宣布封城,媒体们报道,希望春节不要到处串门,这个时候才意识到问题的严重性,但也只是觉得过完春节就没事了。 后面每个城市都出现了患者,确证人数逐日递增 各地医疗援助支援湖北,支援武汉 又出现了每个小区控制进出的策略 湖北省以及武汉市的主要领导被替换 全国各地甚至全球范围口罩都抢不到 多省物流停滞,导致商品堆积 多趟列车以及飞机停止运营 武汉红十字会受到质疑 各种店面关闭,唯独超市还在运营,但人数稀稀拉拉,东西也越来越少 随着时间的推进,除了这些坏消息,也出现了很多的好消息。 全球支援医疗物资 国内工厂拼命的生产医疗物资 各地蔬菜以及肉类支援湖北 一省援助一市策略 调派军医支援湖北 火神山、雷神山、方舱医院 人人自危的关头,仍旧有人站在前线,这就像是一场战争。作为一个个体,不出门添乱也是一种贡献,但是二月中旬该回去上班了,保护好自己也是非常重要的,下面的是关于返程的一些记忆。 从老家出发,自助取票大厅空无一人,进入候车室,人数大约只有平时的 1/30,每个人坐在座位上都和旁边的人隔着 10 个以上的座位。 坐的是 k 车,上车时没有排队,直接进去,找到座位后只有一个人,大概从售票的时候就已经控制票数。k 车的结构是一排 5 个人,中间有个过道划分为 3 个座位和 2 个座位,现在每个人独占 3 个座位或者 2 个座位。火车上要求去杭州的乘客把身份证准备好,但最后也没人来查。火车座位上靠近头部的地方有个白色的布垫子,中间的时候有个乘务员戴着手套全部取走了(可能是消毒或者一次性的)。全程没有各种火车上的叫卖,什么小玩具,修剪工具,内蒙古的奶糖之类的,唯独有盒饭在卖,也只有 10 块钱一份的种类。快下车的时候,乘务员来收拾桌上的垃圾,带着手套,不仅收走了垃圾,连盛放垃圾的盘子也一并回收。 下车后,来自重点疫区的走单独的通道,需要填表格以及量体温;来自一般地区的人则需要用身份证录入到一个系统中(可能是验证杭州健康码?)。火车出站口没有了昔日各种旅馆旅行社的身影。地铁一次体温检测,下车后暴雨如注,叫了辆滴滴,坐在城市看着外面下着的雨。到小区门口了,根据要求填写各种表格,主要的就是《自愿隔离表》,填写后拿个温度计,体温记录表和代购券就可以进门进行自我隔离了。7 天的泡面生活开始。 整个旅程大概五个小时,我的观察只是这个大社会微小的一部分。完。