CPU有L1、L2、L3,但是在CPU和一级缓存中间还有一个缓冲区, 也就是说,CPU往L1中写数据时不会一个字节写一次,而且先往一个缓冲区写,等缓冲区满了之后再一次性写入L1,这个缓冲区由于特别贵,所以很小,只有4个字节。

    程序证明:

    1. public final class WriteCombining {
    2. //循环次数
    3. private static final int ITERATIONS = Integer.MAX_VALUE;
    4. //数组长度
    5. private static final int ITEMS = 1 << 24;
    6. //混淆值,保证写入数据随机
    7. private static final int MASK = ITEMS - 1;
    8. //创建6个字节数组,数组长度为 ITEMS
    9. private static final byte[] arrayA = new byte[ITEMS];
    10. private static final byte[] arrayB = new byte[ITEMS];
    11. private static final byte[] arrayC = new byte[ITEMS];
    12. private static final byte[] arrayD = new byte[ITEMS];
    13. private static final byte[] arrayE = new byte[ITEMS];
    14. private static final byte[] arrayF = new byte[ITEMS];
    15. /**
    16. * 一次循环写入3个字节, 循环3次
    17. * @return
    18. */
    19. public static long runCaseOne() {
    20. long start = System.nanoTime(); //计时开始
    21. int i = ITERATIONS;
    22. //一个循环写3个字节
    23. while (--i != 0) {
    24. int slot = i & MASK;
    25. byte b = (byte) i; //占用一个字节写入
    26. arrayA[slot] = b; //写入一个字节
    27. arrayB[slot] = b; //写入一个字节
    28. }
    29. //一个循环写3个字节
    30. while (--i != 0) {
    31. int slot = i & MASK;
    32. byte b = (byte) i; //占用一个字节写入
    33. arrayC[slot] = b; //写入一个字节
    34. arrayD[slot] = b; //写入一个字节
    35. }
    36. //一个循环写3个字节
    37. while (--i != 0) {
    38. int slot = i & MASK;
    39. byte b = (byte) i; //占用一个字节写入
    40. arrayE[slot] = b; //写入一个字节
    41. arrayF[slot] = b; //写入一个字节
    42. }
    43. return System.nanoTime() - start;
    44. }
    45. /**
    46. * 一个循环写4个字节, 循环2次
    47. * @return
    48. */
    49. public static long runCaseTwo() {
    50. long start = System.nanoTime(); //计时开始
    51. int i = ITERATIONS;
    52. //一个循环写4个字节
    53. while (--i != 0) {
    54. int slot = i & MASK;
    55. byte b = (byte) i; //占用一个字节写入
    56. arrayA[slot] = b; //写入一个字节
    57. arrayB[slot] = b; //写入一个字节
    58. arrayC[slot] = b; //写入一个字节
    59. }
    60. //再来一个循环写4个字节
    61. i = ITERATIONS;
    62. while (--i != 0) {
    63. int slot = i & MASK;
    64. byte b = (byte) i; //占用一个字节写入
    65. arrayD[slot] = b; //写入一个字节
    66. arrayE[slot] = b; //写入一个字节
    67. arrayF[slot] = b; //写入一个字节
    68. }
    69. return System.nanoTime() - start;
    70. }
    71. public static void main(final String[] args) {
    72. //测试3次看结果
    73. for (int i = 1; i <= 3; i++) {
    74. System.out.println(i + " write three bytes one loop duration (ns) = " + runCaseOne());
    75. System.out.println(i + " write four bytes one loop duration (ns) = " + runCaseTwo());
    76. }
    77. }
    78. }

    结果:
    image.png
    按照一般来讲两个方法的性能应该是差不多的。因为循环的次数是一样多的。但是从结果看runCaseTwo要比runCaseOne快很多, 这是因为runCaseTwo利用了CPU的合并写的缓冲区,每次循环正好填满一次缓冲区的4个字节。 而runCaseOne每次循环只往缓冲区写了3个字节,这时缓冲区还要等待下次循环写入一个字节才能刷新缓冲区。因此runCaseTwo要比runCaseOne快很多