IO 性能 (相对于 CPU 性能) 探索分析
体验一: 电脑经常卡顿
公司发的笔记本电脑, 硬件配置 CPU i5 六代, 内存 8G, 机械硬盘无固态. 每天编译一个富客户端 GUI 工程的时候, 经常会导致电脑卡顿, CPU 与内存往往都还没有达到峰值, 磁盘显示 100%
体验二: IO 线程比 UI 线程后退出.
客户端应用在退出的时候, 客户端 UI 界面其实已经消失了, 但是客户端的日志文件往往在 UI 界面消失几秒后才写完 -- 也就是 IO 线程比 UI 线程要慢好几秒.
体验三: 如下例子.(运行环境: 8G 内存, i5 六代, 固态硬盘, 台式机)
例一: 计数 10 万, 计算毫秒数
- public class TestWithNoIO {
- public static void main(String[] args) {
- long begin = System.currentTimeMillis();
- int i = 100000;
- for (int j = 0; j < i; j++) {
- }
- System.out.println("testNonIO 耗时(毫秒):" + (System.currentTimeMillis() - begin));
- }
- }
运行结果:
testNonIO 耗时(毫秒):0
运行许多次, 数值稳定在 1 毫秒或者 0 毫秒
例二: 在 for 循环中增加 System.out.print(""), 注意是没有空格的空字符串, 如下:
- public class TestWithNOLR {
- public static void main(String[] args) {
- long begin = System.currentTimeMillis();
- int i = 100000;
- for (int j = 0; j < i; j++) {
- System.out.print("");
- }
- System.out.println("testIOWithNoLR 耗时:"+(System.currentTimeMillis()-begin));
- }
- }
运行结果:
testIOWithNoLR 耗时: 13
运行多次, 数值稳定在 10 毫秒左右.
分析: 虽然实际上什么也没有输出, 但是仅仅因为使用了 IO, 耗时已经明显增加.
例三: 这次我们在例二的基础上, 空字符串里添加一个空格. 如下:
- public class TestWithNOLR {
- public static void main(String[] args) {
- long begin = System.currentTimeMillis();
- int i = 100000;
- for (int j = 0; j < i; j++) {
- System.out.print(" ");
- }
- System.out.println("testIOWithNoLR 耗时:"+(System.currentTimeMillis()-begin));
- }
- }
运行结果:
testIOWithNoLR 耗时: 187
运行多次, 数值稳定在 180 毫秒左右(注意: 空格输出已被我截去)
分析: IO 有了实际输出量(增加了输出内容), 耗时大大增加
例四: 这次在例三的基础上, 多增加一个空格. 发现在例三的基础上多了 10 毫秒的时间.
例五: 这次我们把 IO 由不换行的输出字符串, 改为输出换行
- public class TestWithLR {
- public static void main(String[] args) {
- long begin = System.currentTimeMillis();
- int i = 100000;
- for (int j = 0; j < i; j++) {
- System.out.println();
- }
- System.out.println("testIOWithLR 耗时:"+(System.currentTimeMillis()-begin));
- }
- }
运行结果:
testIOWithLR 耗时: 198
运行多次, 我们发现数值稳定在 190 多毫秒, 基本上和例四所消耗的时间持平(也就是输出两个空格).
例六: 这次我们在例五的基础上增加一个换行
- public class TestWithLR {
- public static void main(String[] args) {
- long begin = System.currentTimeMillis();
- int i = 100000;
- for (int j = 0; j < i; j++) {
- System.out.println();
- System.out.println();
- }
- System.out.println("testIOWithLR 耗时:"+(System.currentTimeMillis()-begin));
- }
- }
运行结果:
testIOWithLR 耗时: 393
运行多次, 我们发现数值稳定在 400 毫秒左右.
分析: 换行数量翻倍以后, IO 耗时翻倍. 继续增加换行, 发现非常符合这个规律.
以上是输出到控制台, 大家可以试一下输出到机械硬盘, 输出到固态硬盘.
总结:
IO 效率真的是比 CPU 效率低很多;
IO 增加, 耗时会增加;
换行的 IO 增加, 比不换行的 IO 增加, 耗时明显增加要快得多得多.
启发:
日志角度:
不要在生产的代码中嵌入任何 System.out.print. 原因有三点, a. 没保存的日志属于无效日志; b. 性能下降(上面的例子只是以毫秒为单位);c, 输出可能重定向;
如果程序中 IO 比较多的时候, 尽量实现 IO 线程和纯 CPU 线程分离(通常的日志框架就是这么做的, 例如 log4j 或者 logback, 通常是单独的线程在运行). 占 CPU 的线程处理核心业务逻辑, 占 IO 的线程处理日志或其他异步就可以完成的内容.
日志输出尽量不要太频繁(例如在循环中高频输出日志), 能少一行日志就少一行日志, 能少一截尽量少输出一截(从垃圾回收角度来说也应该这么做, 日志里太多字符串)
数据库角度:
设计数据库的时候, 如果有些字段可用枚举尽量用枚举在内存中存储(减少磁盘 IO);
...... 未完待续
可参考资料:
一张表字段多为何要拆表: https://www.cnblogs.com/hzhuxin/p/7985452.html
来源: https://www.cnblogs.com/InformationGod/p/10847270.html