Java: counting/profiling 同步调用
Java: counting/profiling synchronization calls
我正在寻找一种方法来列出 运行 并行 java 应用程序的所有同步调用,以便检测可伸缩性问题(根据 threads/cores)。据我了解,每次输入同步块时,机器都需要同步缓存。这会影响所有 CPU 运行(以多种方式,如内存带宽),即使 运行 任务未因进入同步区域而被阻塞。
设置:
我有一个在更高级别并行化的大型应用程序,即它具有并行执行的复杂任务。并行化在术语中起作用,即所有内核都处于负载状态并且我没有阻塞线程。性能仍然没有随内核扩展,这可能有几个原因。我感兴趣的特定可能原因是是否有很多同步调用(例如,进入同步块、使用锁等)。
任务
我想找出在我的代码中(实际执行的)哪些地方有这样的同步调用,以及每个同步实际执行的频率。有很多引用的库,因此不可能只对同步关键字或类似的东西使用常规代码搜索,因为这会搜索很多从未执行过的代码并带来很多误报。完美的解决方案是拥有一个分析器,它列出所有已执行的同步位置和调用次数。但是,我尝试过的分析器只允许对方法调用进行计数。因此,这里的问题是找到所有实际相关的方法。
或者,如果我能找到一些入口点(主方法)引用的同步位置,它也会有所帮助。 IE。通过递归地遍历代码并检查所有引用的方法、类 等以实现此类同步。在这种情况下,稍后可以使用常规分析器找出频率。
问题
是否有工具或工作流程能够为更大的项目归档上述任务。
提前感谢您的回答。
您可以使用 CPU 分析器来执行此操作。如果你有一个需要很长时间才能获得锁的同步方法,它看起来会花费很长时间。如果时间不长的话,可以不用管。
现在,如果一个方法耗时很长,可能会分不清是不是同步锁。如果你真的不能告诉 运行 阅读代码,你可以将实现移动到一个私有方法,而 public 方法所做的只是获取锁。这将使延迟是在获取锁还是运行宁代码时变得更加清楚。
使用分析器的另一个原因;当你猜测问题可能是什么时,它几乎从来都不是你最初想到的,即使你已经对程序进行性能调优 Java 十年,你首先想到的可能是前 5 名或前 10 名但很少是你遇到的最大问题。
这是一个 java 应用程序,因此您可以在 jdk1.8.XX.XX\bin[=13 中使用 jdk 工具=].使用 visualVM 或 jmc(java 任务控制),您可以可视化线程、锁。
并且您可以在应用程序(log4j 或其他工具)中添加日志以计算执行时间。
除非在此块上存在争用,否则进入和离开同步块是相当便宜的操作。在无竞争的情况下 synchronized
只是一个原子 CAS,或者如果 UseBiasedLocking
优化成功则几乎是一个无操作。虽然看起来可以使用 Instrumentation API 进行同步分析器,但这没有多大意义。
多线程应用程序的问题是争用同步。 JVM 有一些内部计数器来监视锁争用 (see this question). Or you can even write a simple ad-hoc tool to track all contended locks using JVMTI events.
然而,不仅锁会引起争用。即使是非阻塞算法也会因共享资源的竞争而受到影响。 Here is a good example 此类可扩展性问题。所以,我同意@PeterLawrey 的观点,最好从 CPU 分析器开始,因为它通常更容易发现性能问题。
以下非常简单的示例表明,在单线程算法中,与单个监视器同步也会花费一些时间。它表明此示例中的同步 BufferedOutputStream 大约慢了 1/4。它将 100 MB 流式传输到 nop 流。更复杂的代码会产生更多的性能下降。
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class BenchmarkTest {
public static void main( String[] args ) throws IOException {
while( true ) {
// testNop();
testSync();
testNoSync();
}
}
static void testNop() throws IOException {
BenchmarkTest test = new BenchmarkTest();
test.out = new OutputStream() {
@Override
public void write( int b ) throws IOException {
// nop
}
};
test.run( " nop OutputStream" );
}
static void testSync() throws IOException {
BenchmarkTest test = new BenchmarkTest();
test.out = new BufferedOutputStream( new OutputStream() {
@Override
public void write( int b ) throws IOException {
// nop
}
}, 32768 );
test.run( " sync BufferedOutputStream" );
}
static void testNoSync() throws IOException {
BenchmarkTest test = new BenchmarkTest();
test.out = new FastBufferedOutputStream( new OutputStream() {
@Override
public void write( int b ) throws IOException {
// nop
}
}, 32768 );
test.run( "no sync BufferedOutputStream" );
}
private OutputStream out;
void run( String testName ) throws IOException {
long time = System.currentTimeMillis();
for( int i = 0; i < 100_000_000; i++ ) {
out.write( i );
}
System.out.println( testName + " time: " + (System.currentTimeMillis() - time) );
}
static public class FastBufferedOutputStream extends OutputStream {
private byte[] buf;
private int count;
private OutputStream out;
/**
* Creates a BufferedOutputStream without synchronized.
*
* @param out the underlying output stream.
*/
public FastBufferedOutputStream( OutputStream out ) {
this( out, 8192 );
}
/**
* Creates a BufferedOutputStream without synchronized.
*
* @param out the underlying output stream.
* @param size the buffer size.
* @exception IllegalArgumentException if size <= 0.
*/
public FastBufferedOutputStream( OutputStream out, int size ) {
this.out = out;
this.buf = new byte[size];
}
/**
* Flush the internal buffer
*
* @throws IOException if any I/O error occur
*/
private void flushBuffer() throws IOException {
if( count > 0 ) {
out.write( buf, 0, count );
count = 0;
}
}
/**
* {@inheritDoc}
*/
@Override
public void write( int b ) throws IOException {
if( count >= buf.length ) {
flushBuffer();
}
buf[count++] = (byte)b;
}
/**
* {@inheritDoc}
*/
@Override
public void write( byte[] b, int off, int len ) throws IOException {
if( len >= buf.length ) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();
out.write( b, off, len );
return;
}
if( len > buf.length - count ) {
flushBuffer();
}
System.arraycopy( b, off, buf, count, len );
count += len;
}
/**
* {@inheritDoc}
*/
@Override
public void flush() throws IOException {
flushBuffer();
out.flush();
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
flushBuffer();
out.close();
}
}
}
我正在寻找一种方法来列出 运行 并行 java 应用程序的所有同步调用,以便检测可伸缩性问题(根据 threads/cores)。据我了解,每次输入同步块时,机器都需要同步缓存。这会影响所有 CPU 运行(以多种方式,如内存带宽),即使 运行 任务未因进入同步区域而被阻塞。
设置:
我有一个在更高级别并行化的大型应用程序,即它具有并行执行的复杂任务。并行化在术语中起作用,即所有内核都处于负载状态并且我没有阻塞线程。性能仍然没有随内核扩展,这可能有几个原因。我感兴趣的特定可能原因是是否有很多同步调用(例如,进入同步块、使用锁等)。
任务
我想找出在我的代码中(实际执行的)哪些地方有这样的同步调用,以及每个同步实际执行的频率。有很多引用的库,因此不可能只对同步关键字或类似的东西使用常规代码搜索,因为这会搜索很多从未执行过的代码并带来很多误报。完美的解决方案是拥有一个分析器,它列出所有已执行的同步位置和调用次数。但是,我尝试过的分析器只允许对方法调用进行计数。因此,这里的问题是找到所有实际相关的方法。
或者,如果我能找到一些入口点(主方法)引用的同步位置,它也会有所帮助。 IE。通过递归地遍历代码并检查所有引用的方法、类 等以实现此类同步。在这种情况下,稍后可以使用常规分析器找出频率。
问题
是否有工具或工作流程能够为更大的项目归档上述任务。
提前感谢您的回答。
您可以使用 CPU 分析器来执行此操作。如果你有一个需要很长时间才能获得锁的同步方法,它看起来会花费很长时间。如果时间不长的话,可以不用管。
现在,如果一个方法耗时很长,可能会分不清是不是同步锁。如果你真的不能告诉 运行 阅读代码,你可以将实现移动到一个私有方法,而 public 方法所做的只是获取锁。这将使延迟是在获取锁还是运行宁代码时变得更加清楚。
使用分析器的另一个原因;当你猜测问题可能是什么时,它几乎从来都不是你最初想到的,即使你已经对程序进行性能调优 Java 十年,你首先想到的可能是前 5 名或前 10 名但很少是你遇到的最大问题。
这是一个 java 应用程序,因此您可以在 jdk1.8.XX.XX\bin[=13 中使用 jdk 工具=].使用 visualVM 或 jmc(java 任务控制),您可以可视化线程、锁。 并且您可以在应用程序(log4j 或其他工具)中添加日志以计算执行时间。
除非在此块上存在争用,否则进入和离开同步块是相当便宜的操作。在无竞争的情况下 synchronized
只是一个原子 CAS,或者如果 UseBiasedLocking
优化成功则几乎是一个无操作。虽然看起来可以使用 Instrumentation API 进行同步分析器,但这没有多大意义。
多线程应用程序的问题是争用同步。 JVM 有一些内部计数器来监视锁争用 (see this question). Or you can even write a simple ad-hoc tool to track all contended locks using JVMTI events.
然而,不仅锁会引起争用。即使是非阻塞算法也会因共享资源的竞争而受到影响。 Here is a good example 此类可扩展性问题。所以,我同意@PeterLawrey 的观点,最好从 CPU 分析器开始,因为它通常更容易发现性能问题。
以下非常简单的示例表明,在单线程算法中,与单个监视器同步也会花费一些时间。它表明此示例中的同步 BufferedOutputStream 大约慢了 1/4。它将 100 MB 流式传输到 nop 流。更复杂的代码会产生更多的性能下降。
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class BenchmarkTest {
public static void main( String[] args ) throws IOException {
while( true ) {
// testNop();
testSync();
testNoSync();
}
}
static void testNop() throws IOException {
BenchmarkTest test = new BenchmarkTest();
test.out = new OutputStream() {
@Override
public void write( int b ) throws IOException {
// nop
}
};
test.run( " nop OutputStream" );
}
static void testSync() throws IOException {
BenchmarkTest test = new BenchmarkTest();
test.out = new BufferedOutputStream( new OutputStream() {
@Override
public void write( int b ) throws IOException {
// nop
}
}, 32768 );
test.run( " sync BufferedOutputStream" );
}
static void testNoSync() throws IOException {
BenchmarkTest test = new BenchmarkTest();
test.out = new FastBufferedOutputStream( new OutputStream() {
@Override
public void write( int b ) throws IOException {
// nop
}
}, 32768 );
test.run( "no sync BufferedOutputStream" );
}
private OutputStream out;
void run( String testName ) throws IOException {
long time = System.currentTimeMillis();
for( int i = 0; i < 100_000_000; i++ ) {
out.write( i );
}
System.out.println( testName + " time: " + (System.currentTimeMillis() - time) );
}
static public class FastBufferedOutputStream extends OutputStream {
private byte[] buf;
private int count;
private OutputStream out;
/**
* Creates a BufferedOutputStream without synchronized.
*
* @param out the underlying output stream.
*/
public FastBufferedOutputStream( OutputStream out ) {
this( out, 8192 );
}
/**
* Creates a BufferedOutputStream without synchronized.
*
* @param out the underlying output stream.
* @param size the buffer size.
* @exception IllegalArgumentException if size <= 0.
*/
public FastBufferedOutputStream( OutputStream out, int size ) {
this.out = out;
this.buf = new byte[size];
}
/**
* Flush the internal buffer
*
* @throws IOException if any I/O error occur
*/
private void flushBuffer() throws IOException {
if( count > 0 ) {
out.write( buf, 0, count );
count = 0;
}
}
/**
* {@inheritDoc}
*/
@Override
public void write( int b ) throws IOException {
if( count >= buf.length ) {
flushBuffer();
}
buf[count++] = (byte)b;
}
/**
* {@inheritDoc}
*/
@Override
public void write( byte[] b, int off, int len ) throws IOException {
if( len >= buf.length ) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();
out.write( b, off, len );
return;
}
if( len > buf.length - count ) {
flushBuffer();
}
System.arraycopy( b, off, buf, count, len );
count += len;
}
/**
* {@inheritDoc}
*/
@Override
public void flush() throws IOException {
flushBuffer();
out.flush();
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
flushBuffer();
out.close();
}
}
}