Processing 处理|程序滞后

Processing 处理|程序滞后,processing,Processing,我是处理新手,我需要制作一个程序,捕获主监视器,在第二个屏幕上显示平均颜色,并使用另一种颜色,通过函数获得的感知主颜色,形成一个螺旋。 问题是程序的延迟太慢了,1FPS。我想这是因为每次我做截图都有太多的事情要做,但我不知道如何让截图更快 还有很多其他问题,但最主要的是。 多谢各位 代码如下: import java.awt.Robot; import java.awt.AWTException; import java.awt.Rectangle; import java.awt.color.

我是处理新手,我需要制作一个程序,捕获主监视器,在第二个屏幕上显示平均颜色,并使用另一种颜色,通过函数获得的感知主颜色,形成一个螺旋。 问题是程序的延迟太慢了,1FPS。我想这是因为每次我做截图都有太多的事情要做,但我不知道如何让截图更快

还有很多其他问题,但最主要的是。 多谢各位

代码如下:

import java.awt.Robot;
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;

PImage screenshot; 

float a = 0;
int blockSize = 20;

int avg_c;
int per_c;


void setup() {
  fullScreen(2); // 1920x1080
  noStroke();
  frame.removeNotify();
}

void draw() { 
  screenshot();
  avg_c = extractColorFromImage(screenshot);
  per_c = extractAverageColorFromImage(screenshot);
  background(avg_c); // Average color
  spiral();
}


void screenshot() {
  try{
    Robot robot_Screenshot = new Robot();
    screenshot = new PImage(robot_Screenshot.createScreenCapture
    (new Rectangle(0, 0, displayWidth, displayHeight)));
  }
  catch (AWTException e){ }
  frame.setLocation(displayWidth/2, 0);
}

void spiral() {
  fill (per_c); 
  for (int i = blockSize; i < width; i += blockSize*2)
  {
    ellipse(i, height/2+sin(a+i)*100, blockSize+cos(a+i)*5, blockSize+cos(a+i)*5);    
    a += 0.001;
  }
}


color extractColorFromImage(PImage screenshot) { // Get average color
    screenshot.loadPixels(); 
    int r = 0, g = 0, b = 0; 
    for (int i = 0; i < screenshot.pixels.length; i++) { 
        color c = screenshot.pixels[i]; 
        r += c>>16&0xFF; 
        g += c>>8&0xFF; 
        b += c&0xFF;
    } 
    r /= screenshot.pixels.length; g /= screenshot.pixels.length; b /= screenshot.pixels.length;
    return color(r, g, b);
}

color extractAverageColorFromImage(PImage screenshot) { // Get lab average color (perceptual)
  float[] average = new float[3];
  CIELab lab = new CIELab();

  int numPixels = screenshot.pixels.length;
  for (int i = 0; i < numPixels; i++) {
    color rgb = screenshot.pixels[i];

    float[] labValues = lab.fromRGB(new float[]{red(rgb),green(rgb),blue(rgb)});

    average[0] += labValues[0];
    average[1] += labValues[1];
    average[2] += labValues[2];
  }

  average[0] /= numPixels;
  average[1] /= numPixels;
  average[2] /= numPixels;

  float[] rgb = lab.toRGB(average);

  return color(rgb[0] * 255,rgb[1] * 255, rgb[2] * 255);
}


public class CIELab extends ColorSpace {

    @Override
    public float[] fromCIEXYZ(float[] colorvalue) {
        double l = f(colorvalue[1]);
        double L = 116.0 * l - 16.0;
        double a = 500.0 * (f(colorvalue[0]) - l);
        double b = 200.0 * (l - f(colorvalue[2]));
        return new float[] {(float) L, (float) a, (float) b};
    }

    @Override
    public float[] fromRGB(float[] rgbvalue) {
        float[] xyz = CIEXYZ.fromRGB(rgbvalue);
        return fromCIEXYZ(xyz);
    }

    @Override
    public float getMaxValue(int component) {
        return 128f;
    }

    @Override
    public float getMinValue(int component) {
        return (component == 0)? 0f: -128f;
    }    

    @Override
    public String getName(int idx) {
        return String.valueOf("Lab".charAt(idx));
    }

    @Override
    public float[] toCIEXYZ(float[] colorvalue) {
        double i = (colorvalue[0] + 16.0) * (1.0 / 116.0);
        double X = fInv(i + colorvalue[1] * (1.0 / 500.0));
        double Y = fInv(i);
        double Z = fInv(i - colorvalue[2] * (1.0 / 200.0));
        return new float[] {(float) X, (float) Y, (float) Z};
    }

    @Override
    public float[] toRGB(float[] colorvalue) {
        float[] xyz = toCIEXYZ(colorvalue);
        return CIEXYZ.toRGB(xyz);
    }

    CIELab() {
        super(ColorSpace.TYPE_Lab, 3);
    }

    private double f(double x) {
        if (x > 216.0 / 24389.0) {
            return Math.cbrt(x);
        } else {
            return (841.0 / 108.0) * x + N;
        }
    }

    private double fInv(double x) {
        if (x > 6.0 / 29.0) {
            return x*x*x;
        } else {
            return (108.0 / 841.0) * (x - N);
        }
    }


    private final ColorSpace CIEXYZ =
        ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);

    private final double N = 4.0 / 29.0;
}

是的,在我的机器上大约1 FPS:

优化代码可能非常困难,所以我没有阅读所有的东西来寻找改进的东西,而是从测试你在哪里失去了这么多的处理能力开始。答案是这样的:

per_c = extractAverageColorFromImage(screenshot);
void screenshot() {
  try {
    Robot robot_Screenshot = new Robot();
    screenshot = new PImage(robot_Screenshot.createScreenCapture(new Rectangle(0, 0, displayWidth, displayHeight)));
    // ADD THE NEXT LINE
    screenshot.resize(width/4, height/4);
  }
  catch (AWTException e) {
  }
  frame.setLocation(displayWidth/2, 0);
}
extractAverageColorFromImage方法写得很好,但它低估了它必须完成的工作量。屏幕大小与屏幕像素数之间存在二次关系,因此屏幕越大,情况越糟。这种方法一直在处理屏幕截图的每个像素,每个屏幕截图有几次

对于一个普通的颜色来说,这是一个很大的工作。现在,如果有一种方法可以走捷径。。。也许是更小的屏幕,或者更小的屏幕截图。。。哦有!让我们调整屏幕截图的大小。毕竟,我们不需要详细讨论平均值的单个像素。在屏幕截图方法中,添加以下行:

per_c = extractAverageColorFromImage(screenshot);
void screenshot() {
  try {
    Robot robot_Screenshot = new Robot();
    screenshot = new PImage(robot_Screenshot.createScreenCapture(new Rectangle(0, 0, displayWidth, displayHeight)));
    // ADD THE NEXT LINE
    screenshot.resize(width/4, height/4);
  }
  catch (AWTException e) {
  }
  frame.setLocation(displayWidth/2, 0);
}
我将工作量除以4,但我鼓励您调整这个数字,直到您获得最快的满意结果。这只是一个概念证明:

如你所见,调整屏幕截图的大小并使其缩小4倍,使我的速度提高了10倍。这不是一个奇迹,但它好得多,我看不出最终结果有什么不同——但关于这一部分,你必须使用自己的判断,因为你是知道你的项目的人。希望能有帮助


玩得开心

是的,在我的机器上大约1 FPS:

优化代码可能非常困难,所以我没有阅读所有的东西来寻找改进的东西,而是从测试你在哪里失去了这么多的处理能力开始。答案是这样的:

per_c = extractAverageColorFromImage(screenshot);
void screenshot() {
  try {
    Robot robot_Screenshot = new Robot();
    screenshot = new PImage(robot_Screenshot.createScreenCapture(new Rectangle(0, 0, displayWidth, displayHeight)));
    // ADD THE NEXT LINE
    screenshot.resize(width/4, height/4);
  }
  catch (AWTException e) {
  }
  frame.setLocation(displayWidth/2, 0);
}
extractAverageColorFromImage方法写得很好,但它低估了它必须完成的工作量。屏幕大小与屏幕像素数之间存在二次关系,因此屏幕越大,情况越糟。这种方法一直在处理屏幕截图的每个像素,每个屏幕截图有几次

对于一个普通的颜色来说,这是一个很大的工作。现在,如果有一种方法可以走捷径。。。也许是更小的屏幕,或者更小的屏幕截图。。。哦有!让我们调整屏幕截图的大小。毕竟,我们不需要详细讨论平均值的单个像素。在屏幕截图方法中,添加以下行:

per_c = extractAverageColorFromImage(screenshot);
void screenshot() {
  try {
    Robot robot_Screenshot = new Robot();
    screenshot = new PImage(robot_Screenshot.createScreenCapture(new Rectangle(0, 0, displayWidth, displayHeight)));
    // ADD THE NEXT LINE
    screenshot.resize(width/4, height/4);
  }
  catch (AWTException e) {
  }
  frame.setLocation(displayWidth/2, 0);
}
我将工作量除以4,但我鼓励您调整这个数字,直到您获得最快的满意结果。这只是一个概念证明:

如你所见,调整屏幕截图的大小并使其缩小4倍,使我的速度提高了10倍。这不是一个奇迹,但它好得多,我看不出最终结果有什么不同——但关于这一部分,你必须使用自己的判断,因为你是知道你的项目的人。希望能有帮助


玩得开心

不幸的是,我无法提供像laancelot+1这样的详细答案,但希望我能提供一些提示:

调整图像大小绝对是一个好的方向。请记住,您也可以跳过多个像素,而不是增加每个像素。如果正确处理像素索引,则可以在不调用resize的情况下获得类似的调整大小效果,尽管这不会节省大量CPU时间 不要每秒多次创建新的Robot实例。在安装程序中创建一次并重新使用。这更是一个好习惯 使用一个CPU分析器,比如中的一个,看看到底什么是慢的,并首先优化最慢的东西。 第1点示例:

第3点示例:

请注意,最慢的位实际上是来自RGB和Math.cbrt的AWT
我建议找到另一种替代的RGB->XYZ->L*a*b*转换方法,它主要是函数更简单,类更少,具有AWT或其他依赖项,并且希望更快。

不幸的是,我不能提供像laancelot+1这样的详细答案,但希望我能提供一些提示:

调整图像大小绝对是一个好的方向。请记住,您也可以跳过多个像素,而不是增加每个像素。如果正确处理像素索引,则可以在不调用resize的情况下获得类似的调整大小效果,尽管这不会节省大量CPU时间 不要每秒多次创建新的Robot实例。在安装程序中创建一次并重新使用。这更是一个好习惯 使用CPU探查器(如中的一个)查看exa ctly很慢,首先要优化最慢的东西。 第1点示例:

第3点示例:

请注意,最慢的位实际上是来自RGB和Math.cbrt的AWT
我建议找到另一种替代的RGB->XYZ->L*a*b*转换方法,它主要是函数更简单,类更少,具有AWT或其他依赖项,并且希望更快。

可以做很多事情,甚至比前面提到的还要多

迭代与线程 截图后,立即在缓冲图像的每1/N像素(可能是每4或8像素)上迭代。然后,在此迭代过程中,计算每个像素的LAB值,因为每个像素通道都直接可用,同时增加每个RGB通道的运行总数

这样可以避免重复相同像素两次,并避免不必要的BuffereImage转换→ 皮梅奇;以及从图像像素合成然后分解像素通道

类似地,我们避免了处理另一个答案中建议的昂贵的调整大小调用,这不是我们想要调用每个帧的东西,即使它确实加快了程序的速度,但这不是一种有效的方法

现在,在迭代更改的基础上,我们可以将迭代封装在一个可调用的容器中,以便轻松地跨多个系统线程并发运行工作负载毕竟,像素迭代是令人尴尬的并行;下面的示例使用两个线程来实现这一点,每个线程截屏并处理一半的显示像素

优化RGB→XYZ→实验室转换 我们不太关心向后转换,因为每帧只有一个值

看起来您已经实现了XYZ→你自己做实验,并且正在使用RGB→来自java.awt.color的XYZ转换器

如前所述,正向转换XYZ→实验室使用cbrt作为瓶颈。我还认为RGB→XYZ实现对Math.Powx进行了3次调用,每像素2.4-3个非整数指数大大增加了计算量。解决方案是更快的数学

贾法马 是java.math替代品中的一部分-只需导入库并替换任何数学。用FastMath调用。为了获得免费的加速,您可以用精度更低、速度更快的基于LUT的专用类来交换Jafama的E-15精度

因此,至少,将Math.cbrt替换为FastMath.cbrt。然后考虑实施RGB→再次使用Jafama代替java.math

您甚至可能会发现,对于这样一个项目,仅转换为XYZ是一个足够的颜色空间,可以用来克服RGB的众所周知的缺点,从而避免使用XYZ→实验室转换

缓存实验室计算

除非大多数像素正在改变每一帧,然后考虑缓存每个像素的Lab值,只有当像素在当前帧之间改变时才重新计算它。这里的折衷是根据每个像素的前一个值检查每个像素的开销,以及正检查将节省多少计算量。考虑到实验室的计算要昂贵得多,在这里是非常值得的。下面的示例使用这种技术

截屏 无论程序的其余部分如何优化,AWT机器人的CreateScreateScreenCapture都是一个相当大的瓶颈。在足够大的显示器上,它很难超过30FPS。我不能提供任何确切的建议,但值得看看Java中的其他屏幕捕获方法

带迭代更改和线程的返工代码 这段代码实现了上面讨论的内容,减去对实验室计算的任何更改

float a = 0;
int blockSize = 20;

int avg_c;
int per_c;

java.util.concurrent.ExecutorService threadPool = java.util.concurrent.Executors.newFixedThreadPool(4);

List<java.util.concurrent.Callable<Boolean>> taskList;

float[] averageLAB;
int totalR = 0, totalG = 0, totalB = 0; 

CIELab lab = new CIELab();

final int pixelStride = 8; // look at every 8th pixel


void setup() {
  size(800, 800, FX2D);
  noStroke();
  frame.removeNotify();

  taskList = new ArrayList<java.util.concurrent.Callable<Boolean>>();

  Compute thread1 = new Compute(0, 0, width, height/2);
  Compute thread2 = new Compute(0, height/2, width, height/2);
  taskList.add(thread1);
  taskList.add(thread2);
}

void draw() { 

  totalR = 0; // re init
  totalG = 0; // re init
  totalB = 0; // re init 
  averageLAB = new float[3]; // re init

  final int numPixels = (width*height)/pixelStride;

  try {
    threadPool.invokeAll(taskList); // run threads now and block until completion of all
  }
  catch (Exception e) {
    e.printStackTrace();
  }

  // calculate average LAB
  averageLAB[0]/=numPixels;
  averageLAB[1]/=numPixels;
  averageLAB[2]/=numPixels;

  final float[] rgb = lab.toRGB(averageLAB);
  per_c = color(rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);

  // calculate average RGB
  totalR/=numPixels;
  totalG/=numPixels;
  totalB/=numPixels;

  avg_c = color(totalR, totalG, totalB);

  background(avg_c); // Average color
  spiral();
  fill(255, 0, 0);
  text(frameRate, 10, 20);
}

class Compute implements java.util.concurrent.Callable<Boolean> {

  private final Rectangle screenRegion;
  private Robot robot_Screenshot;
  private final int[] previousRGB;
  private float[][] previousLAB;

  Compute(int x, int y, int w, int h) {

    screenRegion = new Rectangle(x, y, w, h);

    previousRGB = new int[w*h];
    previousLAB = new float[w*h][3];

    try {
      robot_Screenshot = new Robot();
    } 
    catch (AWTException e1) {
      e1.printStackTrace();
    }
  }

  @Override
    public Boolean call() {

    BufferedImage rawScreenshot = robot_Screenshot.createScreenCapture(screenRegion);  

    int[] ssPixels = new int[rawScreenshot.getWidth()*rawScreenshot.getHeight()]; // screenshot pixels

    rawScreenshot.getRGB(0, 0, rawScreenshot.getWidth(), rawScreenshot.getHeight(), ssPixels, 0, rawScreenshot.getWidth()); // copy buffer to int[] array

    for (int pixel = 0; pixel < ssPixels.length; pixel+=pixelStride) {

      // get invididual colour channels
      final int pixelColor = ssPixels[pixel];
      final int R = pixelColor >> 16 & 0xFF;
      final int G = pixelColor >> 8 & 0xFF;
      final int B = pixelColor & 0xFF;

      if (pixelColor != previousRGB[pixel]) { // if pixel has changed recalculate LAB value
        float[] labValues = lab.fromRGB(new float[]{R/255f, G/255f, B/255f}); // note that I've fixed this; beforehand you were missing the /255, so it was always white.
        previousLAB[pixel] = labValues;
      }

      averageLAB[0] += previousLAB[pixel][0];
      averageLAB[1] += previousLAB[pixel][1];
      averageLAB[2] += previousLAB[pixel][2];

      totalR+=R;
      totalG+=G;
      totalB+=B;

      previousRGB[pixel] = pixelColor; // cache last result
    }
    return true;
  }
}
800x800px;像素步长=4;相当静态的屏幕背景


有很多事情可以做,甚至超出了已经提到的范围

迭代与线程 截图后,立即在缓冲图像的每1/N像素(可能是每4或8像素)上迭代。然后,在此迭代过程中,计算每个像素的LAB值,因为每个像素通道都直接可用,同时增加每个RGB通道的运行总数

这样可以避免重复相同像素两次,并避免不必要的BuffereImage转换→ 皮梅奇;以及从图像像素合成然后分解像素通道

类似地,我们避免了处理另一个答案中建议的昂贵的调整大小调用,这不是我们想要调用每个帧的东西,即使它确实加快了程序的速度,但这不是一种有效的方法

现在,在迭代更改的基础上,我们可以将迭代封装在一个可调用的容器中,以便轻松地跨多个系统线程并发运行工作负载毕竟,像素迭代是令人尴尬的并行;下面的示例使用两个线程来实现这一点,每个线程截屏并处理一半的显示像素

优化RGB→XYZ→实验室转换 我们不太关心向后转换,因为每帧只有一个值

看起来您已经实现了XYZ→试验自己 并且正在使用RGB→来自java.awt.color的XYZ转换器

如前所述,正向转换XYZ→实验室使用cbrt作为瓶颈。我还认为RGB→XYZ实现对Math.Powx进行了3次调用,每像素2.4-3个非整数指数大大增加了计算量。解决方案是更快的数学

贾法马 是java.math替代品中的一部分-只需导入库并替换任何数学。用FastMath调用。为了获得免费的加速,您可以用精度更低、速度更快的基于LUT的专用类来交换Jafama的E-15精度

因此,至少,将Math.cbrt替换为FastMath.cbrt。然后考虑实施RGB→再次使用Jafama代替java.math

您甚至可能会发现,对于这样一个项目,仅转换为XYZ是一个足够的颜色空间,可以用来克服RGB的众所周知的缺点,从而避免使用XYZ→实验室转换

缓存实验室计算

除非大多数像素正在改变每一帧,然后考虑缓存每个像素的Lab值,只有当像素在当前帧之间改变时才重新计算它。这里的折衷是根据每个像素的前一个值检查每个像素的开销,以及正检查将节省多少计算量。考虑到实验室的计算要昂贵得多,在这里是非常值得的。下面的示例使用这种技术

截屏 无论程序的其余部分如何优化,AWT机器人的CreateScreateScreenCapture都是一个相当大的瓶颈。在足够大的显示器上,它很难超过30FPS。我不能提供任何确切的建议,但值得看看Java中的其他屏幕捕获方法

带迭代更改和线程的返工代码 这段代码实现了上面讨论的内容,减去对实验室计算的任何更改

float a = 0;
int blockSize = 20;

int avg_c;
int per_c;

java.util.concurrent.ExecutorService threadPool = java.util.concurrent.Executors.newFixedThreadPool(4);

List<java.util.concurrent.Callable<Boolean>> taskList;

float[] averageLAB;
int totalR = 0, totalG = 0, totalB = 0; 

CIELab lab = new CIELab();

final int pixelStride = 8; // look at every 8th pixel


void setup() {
  size(800, 800, FX2D);
  noStroke();
  frame.removeNotify();

  taskList = new ArrayList<java.util.concurrent.Callable<Boolean>>();

  Compute thread1 = new Compute(0, 0, width, height/2);
  Compute thread2 = new Compute(0, height/2, width, height/2);
  taskList.add(thread1);
  taskList.add(thread2);
}

void draw() { 

  totalR = 0; // re init
  totalG = 0; // re init
  totalB = 0; // re init 
  averageLAB = new float[3]; // re init

  final int numPixels = (width*height)/pixelStride;

  try {
    threadPool.invokeAll(taskList); // run threads now and block until completion of all
  }
  catch (Exception e) {
    e.printStackTrace();
  }

  // calculate average LAB
  averageLAB[0]/=numPixels;
  averageLAB[1]/=numPixels;
  averageLAB[2]/=numPixels;

  final float[] rgb = lab.toRGB(averageLAB);
  per_c = color(rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);

  // calculate average RGB
  totalR/=numPixels;
  totalG/=numPixels;
  totalB/=numPixels;

  avg_c = color(totalR, totalG, totalB);

  background(avg_c); // Average color
  spiral();
  fill(255, 0, 0);
  text(frameRate, 10, 20);
}

class Compute implements java.util.concurrent.Callable<Boolean> {

  private final Rectangle screenRegion;
  private Robot robot_Screenshot;
  private final int[] previousRGB;
  private float[][] previousLAB;

  Compute(int x, int y, int w, int h) {

    screenRegion = new Rectangle(x, y, w, h);

    previousRGB = new int[w*h];
    previousLAB = new float[w*h][3];

    try {
      robot_Screenshot = new Robot();
    } 
    catch (AWTException e1) {
      e1.printStackTrace();
    }
  }

  @Override
    public Boolean call() {

    BufferedImage rawScreenshot = robot_Screenshot.createScreenCapture(screenRegion);  

    int[] ssPixels = new int[rawScreenshot.getWidth()*rawScreenshot.getHeight()]; // screenshot pixels

    rawScreenshot.getRGB(0, 0, rawScreenshot.getWidth(), rawScreenshot.getHeight(), ssPixels, 0, rawScreenshot.getWidth()); // copy buffer to int[] array

    for (int pixel = 0; pixel < ssPixels.length; pixel+=pixelStride) {

      // get invididual colour channels
      final int pixelColor = ssPixels[pixel];
      final int R = pixelColor >> 16 & 0xFF;
      final int G = pixelColor >> 8 & 0xFF;
      final int B = pixelColor & 0xFF;

      if (pixelColor != previousRGB[pixel]) { // if pixel has changed recalculate LAB value
        float[] labValues = lab.fromRGB(new float[]{R/255f, G/255f, B/255f}); // note that I've fixed this; beforehand you were missing the /255, so it was always white.
        previousLAB[pixel] = labValues;
      }

      averageLAB[0] += previousLAB[pixel][0];
      averageLAB[1] += previousLAB[pixel][1];
      averageLAB[2] += previousLAB[pixel][2];

      totalR+=R;
      totalG+=G;
      totalB+=B;

      previousRGB[pixel] = pixelColor; // cache last result
    }
    return true;
  }
}
800x800px;像素步长=4;相当静态的屏幕背景


计算peruc会扼杀你的表现。这是你可以获得FPS的线路。@laancelot哦,好的,谢谢。我能做些什么来保持改进代码的结果?幸运的是,是的。我马上给你写点东西。计算每分钟消耗你的性能。这是你可以获得FPS的线路。@laancelot哦,好的,谢谢。我能做些什么来保持改进代码的结果?幸运的是,是的。我马上给你写点东西。非常感谢!我现在将尝试调整我的代码:非常感谢!现在我将尝试调整我的代码:我喜欢跳过一些像素的想法!我对同一行中的某些内容进行了思考,但对它采样像素的方式表示怀疑——尽管我看不出这怎么会出错。现在我也在考虑自己使用CPU分析器。很好的建议。是的,这是正确的,当跳过像那样的像素时,最好是管理索引以在某种程度上均匀采样,否则一些区域可能会采样过多,而另一些区域可能采样不足。CPU探查器很方便:我对一些代码感到惊讶,这些代码的速度出乎意料地慢,并且发现相同的函数在不同的操作系统上更快。此外,内存分析器非常方便地发现内存泄漏。我喜欢跳过一些像素的想法!我对同一行中的某些内容进行了思考,但对它采样像素的方式表示怀疑——尽管我看不出这怎么会出错。现在我也在考虑自己使用CPU分析器。很好的建议。是的,这是正确的,当跳过像那样的像素时,最好是管理索引以在某种程度上均匀采样,否则一些区域可能会采样过多,而另一些区域可能采样不足。CPU探查器很方便:我对一些代码感到惊讶,这些代码的速度出乎意料地慢,并且发现相同的函数在不同的操作系统上更快。此外,内存探查器非常方便地发现内存泄漏。