Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid expensive Throwable.fillInStackTrace call on each batch execution #3996

Closed

Conversation

turbanoff
Copy link
Contributor

When I profiled my application, I noticed suspicious calls to Throwable.fillInStackTrace.

	  at java.lang.Throwable.fillInStackTrace(Throwable.java:798)
	  - locked <0x2ce7> (a java.sql.SQLException)
	  at java.lang.Throwable.<init>(Throwable.java:256)
	  at java.lang.Exception.<init>(Exception.java:55)
	  at java.sql.SQLException.<init>(SQLException.java:142)
	  at org.h2.jdbc.JdbcPreparedStatement.executeBatch(JdbcPreparedStatement.java:1273)
	  at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.java:2544)
	  at org.springframework.jdbc.core.JdbcTemplate.lambda$batchUpdate$5(JdbcTemplate.java:1143)
	  at org.springframework.jdbc.core.JdbcTemplate$$Lambda$724/0x000001e65b77aea8.doInPreparedStatement(Unknown Source:-1)
	  at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651)
	  at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:691)
	  at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:1127)

изображение

I propose to use ArrayList instead of SQLException as storage for batch exceptions.

@turbanoff turbanoff force-pushed the avoid_fillInStackTrace_in_batch branch from f43c5df to 22392e1 Compare February 2, 2024 15:24
@katzyn
Copy link
Contributor

katzyn commented Feb 3, 2024

Thank you for your contribution!

Did you run any actual tests with JMH? It looks like your code actually works slower in warmed up JVM.

@turbanoff
Copy link
Contributor Author

@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
@Fork(2)
public class BatchInsertBenchmark {

    @Param({"1", "10", "100", "1000"})
    int rows;

    private PreparedStatement prepared;

    @Setup
    public void setup() throws ClassNotFoundException, SQLException {
        Class.forName("org.h2.Driver");
        Connection conn = DriverManager.getConnection("jdbc:h2:mem:test");
        Statement statement = conn.createStatement();
        statement.execute("CREATE table TO_FILL(a int, b text)");
        statement.close();
        prepared = conn.prepareStatement("insert into TO_FILL(a, b) values (?, ? )");
    }

    @Benchmark
    public int[] executeBatch() throws SQLException {
        for (int i = 0; i < rows; i++) {
            prepared.setInt(1, i);
            prepared.setString(2, "value" + i);
            prepared.addBatch();
        }
        return prepared.executeBatch();
    }

    @Benchmark
    public long[] executeLargeBatch() throws SQLException {
        for (int i = 0; i < rows; i++) {
            prepared.setInt(1, i);
            prepared.setString(2, "value" + i);
            prepared.addBatch();
        }
        return prepared.executeLargeBatch();
    }
}

Before patch

Benchmark                               (rows)  Mode  Cnt     Score    Error  Units
BatchInsertBenchmark.executeBatch            1  avgt   10     2,279 ±  0,102  us/op
BatchInsertBenchmark.executeBatch           10  avgt   10    14,206 ±  0,536  us/op
BatchInsertBenchmark.executeBatch          100  avgt   10   134,013 ±  6,345  us/op
BatchInsertBenchmark.executeBatch         1000  avgt   10  1348,812 ± 53,988  us/op
BatchInsertBenchmark.executeLargeBatch       1  avgt   10     2,291 ±  0,114  us/op
BatchInsertBenchmark.executeLargeBatch      10  avgt   10    14,691 ±  0,932  us/op
BatchInsertBenchmark.executeLargeBatch     100  avgt   10   141,596 ±  9,606  us/op
BatchInsertBenchmark.executeLargeBatch    1000  avgt   10  1342,485 ± 49,054  us/op

After patch

Benchmark                               (rows)  Mode  Cnt     Score    Error  Units
BatchInsertBenchmark.executeBatch            1  avgt   10     1,358 ±  0,060  us/op
BatchInsertBenchmark.executeBatch           10  avgt   10    13,319 ±  0,550  us/op
BatchInsertBenchmark.executeBatch          100  avgt   10   134,806 ±  7,097  us/op
BatchInsertBenchmark.executeBatch         1000  avgt   10  1337,233 ± 43,192  us/op
BatchInsertBenchmark.executeLargeBatch       1  avgt   10     1,379 ±  0,083  us/op
BatchInsertBenchmark.executeLargeBatch      10  avgt   10    13,665 ±  0,677  us/op
BatchInsertBenchmark.executeLargeBatch     100  avgt   10   137,312 ±  7,729  us/op
BatchInsertBenchmark.executeLargeBatch    1000  avgt   10  1392,066 ± 30,256  us/op

As I can see there is clear speed up for small count of rows. For bigger count of rows performance change is within error boundaries.

@katzyn
Copy link
Contributor

katzyn commented Feb 3, 2024

I tested another case when there were exceptions.

I have an idea how to speed up both cases, but I need to test it.

@turbanoff
Copy link
Contributor Author

I tested another case when there were exceptions.

I think we are more interesting in case, when there are no exceptions. It's what usually happen during normal application execution.

@katzyn
Copy link
Contributor

katzyn commented Feb 4, 2024

Your fix is good enough for original problem with performance, but there is another one and its solution requires reimplementation of the whole batch execution.

@katzyn
Copy link
Contributor

katzyn commented Feb 4, 2024

Superseded by #3997.

Thank you again for pointing this out!

@katzyn katzyn closed this Feb 4, 2024
@turbanoff turbanoff deleted the avoid_fillInStackTrace_in_batch branch February 5, 2024 08:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants