eclipse-openj9 / openj9

Eclipse OpenJ9: A Java Virtual Machine for OpenJDK that's optimized for small footprint, fast start-up, and high throughput. Builds on Eclipse OMR (https://github.com/eclipse/omr) and combines with the Extensions for OpenJDK for OpenJ9 repo.
Other
3.28k stars 721 forks source link

Balanced GC policy slows the write of a gzip file #16300

Closed dasilvj closed 1 year ago

dasilvj commented 1 year ago

The write of a 50MB gzip file using java.util.zip.GZIPOutputStream takes less than a second using the default GC policy. Using the balanced GC policy (-Xgcpolicy:balanced), it takes a few minutes.

Issue spotted using the following versions on Windows & Linux:

IBM Semeru Runtime
17.0.5.0
OpenJDK 17.0.5+8
OpenJ9 0.35.0
Windows
IBM Semeru Runtime
17.0.4.1
OpenJDK 17.0.4.1+1
OpenJ9 0.33.1
Linux

Reproducible with the following Java main:

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.zip.GZIPOutputStream;

public class TestOpenJ9GCBalancedGZipOutputStreamIssue {

  public static void main(final String[] args) throws IOException {
    int[] NB_VALUES_SCENARIOS = new int[] {
        10_000, 100_000, 1_000_000, 10_000_000
    };

    for (int i = 0; i < NB_VALUES_SCENARIOS.length; i++) {
      final int nbValues = NB_VALUES_SCENARIOS[i];

      final byte[] dataBytes = getDataBytesForNumberOfValues(nbValues);
      final Path newPartitionFile =
          Files.createTempFile(TestOpenJ9GCBalancedGZipOutputStreamIssue.class.getSimpleName(), "");
      System.out.println("Write GZIP file '" + newPartitionFile.toAbsolutePath() + "' with '"
          + nbValues + "' values");
      try (final DataOutputStream dos =
          new DataOutputStream(new GZIPOutputStream(Files.newOutputStream(newPartitionFile)))) {
        final long startTimeInNS = System.nanoTime();
        System.out.println("BEGIN_WRITE [dataBytesLen='" + dataBytes.length + "']");
        dos.write(dataBytes);
        System.out.println("END_WRITE [durationInMillis='"
            + Duration.ofNanos(System.nanoTime() - startTimeInNS).toMillis() + "']");
      } finally {
        if (Files.deleteIfExists(newPartitionFile)) {
          System.out.println("Deleted '" + newPartitionFile.toAbsolutePath() + "'");
        }
      }
    }
  }

  private static final byte[] getDataBytesForNumberOfValues(final int nbValues) throws IOException {
    final Map<String, SortedSet<String>> partitionData = new HashMap<>();
    final SortedSet<String> keyData = new TreeSet<>();
    for (int i = 0; i < nbValues; i++) {
      keyData.add(TestOpenJ9GCBalancedGZipOutputStreamIssue.class.getSimpleName() + i);
    }
    partitionData.put(TestOpenJ9GCBalancedGZipOutputStreamIssue.class.getSimpleName() + "-1",
        keyData);

    final byte[] dataBytes;
    try (final ByteArrayOutputStream dataBOS = new ByteArrayOutputStream()) {
      try (final DataOutputStream dataDOS = new DataOutputStream(dataBOS)) {
        for (final Map.Entry<String, SortedSet<String>> partitionDataEntry : partitionData
            .entrySet()) {
          for (final String dataValue : partitionDataEntry.getValue()) {
            final byte[] dataValueBytes = dataValue.getBytes(StandardCharsets.UTF_8);
            dataDOS.writeInt(dataValueBytes.length);
            dataDOS.write(dataValueBytes);
          }
        }
        dataDOS.flush();
      }

      dataBytes = dataBOS.toByteArray();
    }

    return dataBytes;
  }
}

Default GC main output

Write GZIP file 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue12309753225143979752' with '10000' values
BEGIN_WRITE [dataBytesLen='488890']
END_WRITE [durationInMillis='4']
Deleted 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue12309753225143979752'
Write GZIP file 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue12665477723083095111' with '100000' values
BEGIN_WRITE [dataBytesLen='4988890']
END_WRITE [durationInMillis='37']
Deleted 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue12665477723083095111'
Write GZIP file 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue3017559964530800524' with '1000000' values
BEGIN_WRITE [dataBytesLen='50888890'] // 50MB
END_WRITE [durationInMillis='335']
Deleted 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue3017559964530800524'
Write GZIP file 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue7180190478736843458' with '10000000' values
BEGIN_WRITE [dataBytesLen='518888890'] // 500MB
END_WRITE [durationInMillis='3447']
Deleted 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue7180190478736843458'

Balanced GC main output

Write GZIP file 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue8240261865019036048' with '10000' values
BEGIN_WRITE [dataBytesLen='488890']
END_WRITE [durationInMillis='4']
Deleted 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue8240261865019036048'
Write GZIP file 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue7864815220160303440' with '100000' values
BEGIN_WRITE [dataBytesLen='4988890']
END_WRITE [durationInMillis='1692']
Deleted 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue7864815220160303440'
Write GZIP file 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue9170858169658686901' with '1000000' values
BEGIN_WRITE [dataBytesLen='50888890']
END_WRITE [durationInMillis='177585']
Deleted 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue9170858169658686901'
Write GZIP file 'C:\Users\xxx\AppData\Local\Temp\TestOpenJ9GCBalancedGZipOutputStreamIssue12928817364060116750' with '10000000' values
BEGIN_WRITE [dataBytesLen='518888890']
........... still running (-:
amicic commented 1 year ago

please try with -Xgc:enableArrayletDoubleMapping

dasilvj commented 1 year ago

please try with -Xgc:enableArrayletDoubleMapping

I just tried it, and unfortunately it doesn't seem to help )-:

amicic commented 1 year ago

The option only affects behavior on Linux. Is that where you tried?

dasilvj commented 1 year ago

The option only affects behavior on Linux. Is that where you tried?

Yes, I tried it on CentOS Linux (release 7.9.2009), as the option wasn't even known on Windows (-; I am sure you can easily reproduce the issue locally using the dummy main that I provided.

amicic commented 1 year ago

OK, thanks for quick responses and providing test code. We'll investigate this (cannot promise about the priority) .

amicic commented 1 year ago

Sorry, busy, have not tried the test yet, but another thing to quickly check what's glibc version on that Linux system. Double mapping works on 2.27 onwards. Also, Verbose GC will report if enabling double mapping really succeeded in VGC initialize block.

https://github.com/eclipse/omr/pull/4839

dasilvj commented 1 year ago

Thanks for the info! I quickly checked and the CentOS Linux where I made the test with the double mapping was using the GLIBC 2.17 (so it was probably not enabled properly). I unfortunately don't have much more time to make more tests, I simply removed the usage of gzip for now to workaround the issue.

amicic commented 1 year ago

From JVM point of view, we don't have a short term workaround for this (inefficiency when Java using natives for processing of large arrays), either (other than as already said for newer Linux disros)

Long term (some time next year) we do work on a major redesigned, that will remove this limitation in Balanced GC, for all platforms that we support.

pshipton commented 1 year ago

Isn't there a possible workaround involving increasing the region size?

LinHu2016 commented 1 year ago

yes, increasing the region size bigger than zip size could avoid to using arraylet for accessing the zip, then preventing "copy" during calling jniCriticals, please try below the java options, see if it would help. -Xgcpolicy:balanced -Xmx4G -Xms4G -Xgc:regionSize=67108864 -XXgc:tarokNurseryMaxAge=1 -XXgc:tarokRegionMaxAge=2

dmitripivkine commented 1 year ago

Balanced with -Xgc:enableArrayletDoubleMapping has been tested and it actually helps:

> lsb_release -a
No LSB modules are available.
Distributor ID:    Ubuntu
Description:    Ubuntu 18.04.5 LTS
Release:    18.04
Codename:    bionic

> ldd --version
ldd (Ubuntu GLIBC 2.27-3ubuntu1.4) 2.27

> ./jdk-17.0.5+8/bin/java -Xmx5g -Xgcpolicy:balanced -Xgc:enableArrayletDoubleMapping TestOpenJ9GCBalancedGZipOutputStreamIssue 
Write GZIP file '/tmp/TestOpenJ9GCBalancedGZipOutputStreamIssue4614104854497781635' with '10000' values
BEGIN_WRITE [dataBytesLen='488890']
END_WRITE [durationInMillis='7']
Deleted '/tmp/TestOpenJ9GCBalancedGZipOutputStreamIssue4614104854497781635'
Write GZIP file '/tmp/TestOpenJ9GCBalancedGZipOutputStreamIssue11218347477169836263' with '100000' values
BEGIN_WRITE [dataBytesLen='4988890']
END_WRITE [durationInMillis='44']
Deleted '/tmp/TestOpenJ9GCBalancedGZipOutputStreamIssue11218347477169836263'
Write GZIP file '/tmp/TestOpenJ9GCBalancedGZipOutputStreamIssue13860107713856738643' with '1000000' values
BEGIN_WRITE [dataBytesLen='50888890']
END_WRITE [durationInMillis='394']
Deleted '/tmp/TestOpenJ9GCBalancedGZipOutputStreamIssue13860107713856738643'
Write GZIP file '/tmp/TestOpenJ9GCBalancedGZipOutputStreamIssue9298862989241367693' with '10000000' values
BEGIN_WRITE [dataBytesLen='518888890']
END_WRITE [durationInMillis='3871']
Deleted '/tmp/TestOpenJ9GCBalancedGZipOutputStreamIssue9298862989241367693'

This known problem is going to be addressed with new feature https://github.com/eclipse-openj9/openj9/issues/14005 scheduled to be delivered in 2023. Meanwhile -Xgc:enableArrayletDoubleMapping can be used as workaround (Linux glibc version 2.27+ only)