如何衡量在java平台下在上下文切换中花费的时间

2022-09-02 10:47:44

让我们假设每个线程都在做一些FP计算,我感兴趣的是

  • CPU 用于切换线程而不是运行线程的时间
  • 在共享内存总线上创建了多少同步流量 - 当线程共享数据时,它们必须使用同步机制

我的问题是:如何设计一个测试程序来获得这些数据?


答案 1

您无法轻松区分由于线程切换和内存缓存争用而导致的浪费。您可以测量线程争用。也就是说,在linux上,你可以cat /proc/PID/XXX,并获得大量详细的每线程统计数据。但是,由于先发制人的调度程序不会自欺欺人,因此无论您使用多少线程,您每秒都不会获得超过30 ctx的开关。而这个时间将相对较小,与你正在做的工作量相比。上下文切换的真正代价是缓存污染。例如,一旦您的上下文切换回去,您很可能会遇到大部分缓存未命中。因此,操作系统时间和上下文切换计数的价值最小。

真正有价值的是线程间缓存行脏污物的比例。根据 CPU 的不同,缓存行脏后跟对等 CPU 读取比缓存未命中慢 - 因为您必须强制对等 CPU 将其值写入 main-mem,然后才能开始读取。某些 CPU 允许您从对等缓存行中提取,而不会命中主内存。

因此,关键是绝对最小化任何共享的已修改内存结构。使所有内容尽可能只读。这包括共享 FIFO 缓冲区(包括执行程序池)。也就是说,如果您使用了同步队列 - 则每个 sync-op 都是一个共享的脏内存区域。而且,如果速率足够高,它可能会触发操作系统陷阱停止,等待对等线程的互斥体。

理想的情况是分割RAM,将单个大型工作单元分配给固定数量的工人,然后使用倒计时闩锁或其他一些内存屏障(使得每个线程只能触摸一次)。理想情况下,任何临时缓冲区都是预先分配的,而不是进入和退出共享内存池(这会导致缓存争用)。Java“同步”块利用(幕后)共享哈希表内存空间,从而触发不需要的脏读,我还没有确定java 5 Lock对象是否能避免这种情况,但你仍然利用操作系统的停滞,这对你的吞吐量没有帮助。显然,大多数 OutputStream 操作都会触发此类同步调用(当然,它们通常会填充一个公共流缓冲区)。

一般来说,我的经验是,对于一个普通的字节数组/对象数组等,单线程比多线程更快。至少使用我尝试过的简单排序/过滤算法。根据我的经验,Java和C语言都是如此。我还没有尝试过FPU直观的操作(如除法,sqrt),其中缓存行可能不是一个因素。

基本上,如果你是一个单CPU,你没有缓存行问题(除非操作系统总是刷新缓存,即使在共享线程中),但多线程给你买的比没有少。在超线程中,这是相同的交易。在单 CPU 共享 L2/L3 高速缓存配置(例如 AMD)中,您可能会发现一些好处。在多CPU英特尔总线中,忘记它 - 共享写入内存比单线程更糟糕。


答案 2

为了测量上下文切换需要多长时间,我将运行如下操作:

public static void main(String[] args) {     
    Object theLock = new Object(); 
    long startTime;
    long endtime;
    synchronized( theLock ){
        Thread task = new TheTask( theLock ); 
        task.start();
        try {
             theLock.wait(); 
             endTime = System.currentTimeMillis();
        }
        catch( InterruptedException e ){
             // do something if interrupted
        }
    }
    System.out.println("Context Switch Time elapsed: " + endTime - startTime);
}

class TheTask extends Thread {
    private Object theLock;
    public TheTask( Object theLock ){
        this.theLock = theLock; 
    }
    public void run(){ 
        synchronized( theLock ){
            startTime = System.currentTimeMillis();
            theLock.notify(); 
        }
    }
}

您可能希望多次运行此代码以获得平均值,并确保这两个线程是唯一在计算机中运行的线程(上下文切换仅在这两个线程内发生)。