乾物|パフォーマンス改善キー、コードの詳細によってもたらされる究極の体験

序文

ご存知のとおり、コードはプロジェクトの中核であり、小さなコードがプロジェクト全体のエクスペリエンスに影響を与える可能性があります。成長から成熟までの0から1までのプロジェクトは、コードを注意深く磨くことと切り離せません。詳細は成功または失敗を決定し、これはまさに優れたオープンソースプロジェクトです。この記事では、ShardingSphere 5.1.0のパフォーマンスの向上を例として取り上げ、コードの詳細によってもたらされる究極のエクスペリエンスとその方法を示します。コードを飛躍させます。

Wu Weijie、SphereExインフラストラクチャR&Dエンジニア、ApacheShardingSphereコミッター。現在、ApacheShardingSphereとそのサブプロジェクトElasticJobの研究開発に焦点を当てています。

コンテンツを最適化する

オプションの使用方法を修正する

Java 8で導入され、直接メソッドの戻りを回避するなど、コードをより洗練されたものにすることができますより一般的に使用される方法は2つあります。 java.util.Optional  null Optional

public T orElse(T other{
    return value != null ? value : other;
}

public T orElseGet(Supplier<? extends T> other{
    return value != null ? value : other.get();
}

ShardingSphereクラスで使用されるそのようなコードがあります: org.apache.shardingsphere.infra.binder.segment.select.orderby.engine.OrderByContextEngine Optional

Optional<OrderByContext> result = // 省略代码...
return result.orElse(getDefaultOrderByContextWithoutOrderBy(groupByContext));

上記の書き込みメソッドでは、結果の結果が空でなくても、内部のメソッドが呼び出されます。特に、内部のメソッドに変更操作が含まれる場合、予期しないことが発生する可能性があります。メソッド呼び出しの場合は、次のように調整する必要があります。 orElse orElse  orElse 

Optional<OrderByContext> result = // 省略代码...
return result.orElseGet(() -> getDefaultOrderByContextWithoutOrderBy(groupByContext));

ラムダを使用してラムダを提供し、結果が空の場合にのみ内部のメソッドが呼び出されるようにします。 Supplier  orElseGet orElseGet 

関連するPR:https ://github.com/apache/shardingsphere/pull/11459/files

Java8ConcurrentHashMapのcomputeIfAbsentへの高頻度の同時呼び出しを回避します

java.util.concurrent.ConcurrentHashMapこれは、並行シナリオで一般的に使用される一種のマップです。すべての操作と比較してスレッドセーフを確保しながらパフォーマンスを向上させます。ただし、Java 8の実装ではキーが存在する場合でも値はコードブロックで取得されるため、同じキーを頻繁に呼び出す場合の同時実行性のパフォーマンスに大きく影響します。 synchronized  java.util.HashtableConcurrentHashMapConcurrentHashMap  computeIfAbsent  synchronized  computeIfAbsent 

参考:https://bugs.openjdk.java.net/browse/JDK-8161372

この問題はJava9で解決されましたが、Java 8での同時パフォーマンスを確保するために、この問題を回避するためにShardingSphereコードの書き込み方法を調整しました。

として、頻繁に呼び出されるShardingSphereのクラスを取り上げます。 org.apache.shardingsphere.infra.executor.sql.prepare.driver.DriverExecutionPrepareEngine

 // 省略部分代码...
    private static final Map<String, SQLExecutionUnitBuilder> TYPETOBUILDERMAP = new ConcurrentHashMap<>(81);
    // 省略部分代码...
    public DriverExecutionPrepareEngine(final <span class="hljs-builtin" style="font-size: inherit; line-height: inherit; margin: 0px; padding: 0px; color: rgb(170, 87, 60); word-wrap: inherit !important; word-break: inherit !important;">String type, final int maxConnectionsSizePerQuery, final ExecutorDriverManager<C, ?, ?> executorDriverManager, 
                                        final StorageResourceOption option, final Collection<ShardingSphereRule> rules) {
        super(maxConnectionsSizePerQuery, rules);
        this.executorDriverManager = executorDriverManager;
        this.option = option;
        sqlExecutionUnitBuilder = TYPETOBUILDER_MAP.computeIfAbsent(type, 
                key -> TypedSPIRegistry.getRegisteredService(SQLExecutionUnitBuilder.class, key, new Properties()));
    }

上記のコードには2つのタイプしかなく、このコードはほとんどのSQL実行の唯一の方法です。つまり、メソッドは同じキーで同時に頻繁に呼び出され、同時実行のパフォーマンスが制限されます。この問題を次のように回避します。 computeIfAbsent  type computeIfAbsent 

SQLExecutionUnitBuilder result;
if (null == (result = TYPE_TO_BUILDER_MAP.get(type))) {
    result = TYPE_TO_BUILDER_MAP.computeIfAbsent(type, key -> TypedSPIRegistry.getRegisteredService(SQLExecutionUnitBuilder.class, key, new Properties()));
}
return result;

関連するPR:https ://github.com/apache/shardingsphere/pull/13275/files

java.util.Propertiesへの頻繁な呼び出しを避けてください

java.util.Properties これはShardingSphere構成で一般的に使用されるクラスであり、Properties 継承されるため、並行状況で頻繁に呼び出されるメソッドを回避する必要があります。 java.util.Hashtable Properties 

ShardingSphereのデータシャーディングアルゴリズムに関連するクラスには高周波呼び出しロジックがあり、その結果、同時実行パフォーマンスが制限されることがわかりました。私たちのアプローチは、シャーディングアルゴリズムでの計算ロジックの同時実行を回避するためにメソッド呼び出しに関連するロジックをメソッドに配置することです。 org.apache.shardingsphere.sharding.algorithm.sharding.inline.InlineShardingAlgorithm getProperty Properties  InlineShardingAlgorithm init 

関連するPR:https ://github.com/apache/shardingsphere/pull/13282/files

Collections.synchronizedMapを避けてください

ShardingSphereのMonitorBlockedをチェックする過程で、このクラスでは高周波読み取りで装飾されたMapが使用されていることがわかりました。これは、同時パフォーマンスに影響を与えます。分析後、変更されたマップには初期化フェーズでの変更操作のみが含まれ、後続の操作はすべて読み取り操作になります。変更メソッドを直接削除できます。 org.apache.shardingsphere.infra.metadata.schema.model.TableMetaData Collections.synchronizedMap Collections.synchronizedMap

関係PR:https ://github.com/apache/shardingsphere/pull/13264/files

不要なString.formatではなく文字列の連結

ShardingSphereのクラスには、次のようなロジックがあります。 org.apache.shardingsphere.sql.parser.sql.common.constant.QuoteCharacter

 public String wrap(final String value{
        return String.format("%s%s%s", startDelimiter, value, endDelimiter);
    }

明らかに、上記のロジックは文字列の連結を行うことですが、メソッドを使用するオーバーヘッドは、直接の文字列の連結よりも大きくなります。次のように変更します。 String.format

public String wrap(final String value{
        return startDelimiter + value + endDelimiter;
    }

JMHを使用して、簡単なテスト、つまりテスト結果を実行します。

# JMH version: 1.33
# VM version: JDK 17.0.1, Java HotSpot(TM) 64-Bit Server VM, 17.0.1+12-LTS-39
# Blackhole mode: full + dont-inline hint (default, use -Djmh.blackhole.autoDetect=true to auto-detect)
# Warmup: 3 iterations, 5 s each
# Measurement: 3 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 16 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
Benchmark                           Mode  Cnt          Score         Error  Units
StringConcatBenchmark.benchFormat  thrpt    9   28490416.644 ± 1377409.528  ops/s
StringConcatBenchmark.benchPlus    thrpt    9  163475708.153 ± 1748461.858  ops/s

連結文字列を使用するオーバーヘッドは、連結文字列を使用するオーバーヘッドよりも大きく、直接連結文字列のパフォーマンスはJava9以降最適化されていることがわかりますこれは、適切な文字列連結方法を選択することの重要性を示しています。 String.format  +

関連するPR:https ://github.com/apache/shardingsphere/pull/11291/files

高周波ストリームの代わりにfor-eachを使用

ShadingSphere5.xコードはより多くを使用しました。 java.util.stream.Stream

ShardingSphere-JDBC + openGaussのストレステストを行ったBenchmarkSQL(TPC-CテストのJava実装)のパフォーマンステストで、ストレステスト中に見つかったすべての高周波ストリームをfor-each、ShardingSphere-パフォーマンスに置き換えた後、 JDBCの改善は明らかです。

*注: ShardingSphere-JDBCとopenGaussは、それぞれ2台の128コアaarch64マシンでBishengJDK8を使用します。

上記のテスト結果は、aarch64プラットフォームとJDKにも関連している可能性があります。ただし、ストリーム自体には一定のオーバーヘッドがあり、パフォーマンスはシナリオによって大きく異なります。頻繁に呼び出され、ストリームがパフォーマンスを最適化できるかどうかわからないロジックの場合は、最初にfor-eachループを使用することを検討します。

関連するPR:https ://github.com/apache/shardingsphere/pull/13845/files

不要な論理(繰り返し)呼び出しを避ける

ロジックの不必要な繰り返し呼び出しを避けるために多くの場合があります:

hashCodeの計算

ShardingSphereには、以下を実装およびメソッドするクラスがあります。 org.apache.shardingsphere.sharding.route.engine.condition.Column equals  hashCode

@RequiredArgsConstructor
@Getter
@ToString
public final class Column {

    private final String name;

    private final String tableName;

    @Override
    public boolean equals(final Object obj) {...}

    @Override
    public int hashCode() {
        return Objects.hashCode(name.toUpperCase(), tableName.toUpperCase()); 
    } 
}

明らかに、上記のクラスは不変ですが、メソッドの実装では、メソッドの計算が毎回呼び出されます。このオブジェクトがMapまたはSetで頻繁にアクセスされる場合、多くの不要な計算オーバーヘッドが発生します。 hashCode  hashCode

調整済み:

@Getter
@ToString
public final class Column {

    private final String name;

    private final String tableName;

    private final int hashCode;

    public Column(final String name, final String tableName) {
        this.name = name;
        this.tableName = tableName;
        hashCode = Objects.hash(name.toUpperCase(), tableName.toUpperCase());
    }

    @Override
    public boolean equals(final Object obj) {...}

    @Override
    public int hashCode() {
        return hashCode;
    } 
}

関連するPR:https ://github.com/apache/shardingsphere/pull/11760/files

リフレクションの代わりにラムダを使用してメソッドを呼び出す

ShardingSphereソースコードには、メソッドとパラメーターの呼び出しを記録し、必要に応じて指定されたオブジェクトへのメソッド呼び出しを再生する必要がある次のシナリオがあります。

  1. beginなどのステートメントをShardingSphere-Proxyに送信します。

  2. ShardingSpherePreparedStatementを使用して、指定した場所のプレースホルダーのパラメーターを設定します。

次のコードを例にとると、リファクタリングの前に、リファクタリングを使用してメソッド呼び出しを記録し、それらを再生します。リフレクションメソッド自体には一定のパフォーマンスオーバーヘッドがあり、コードの可読性は良くありません。

@Override
public void begin() {
    recordMethodInvocation(Connection.class, "setAutoCommit"new Class[]{boolean.class}, new Object[]{false});
}

リファクタリング後、リフレクションを使用してメソッドを呼び出すオーバーヘッドが回避されます。

@Override
public void begin() {
    connection.getConnectionPostProcessors().add(target -> {
        try {
            target.setAutoCommit(false);
        } catch (final SQLException ex) {
            throw new RuntimeException(ex);
        }
    });
}

関連するPR:

https://github.com/apache/shardingsphere/pull/10466/files

https://github.com/apache/shardingsphere/pull/11415/files

aarch64のNettyEpollサポート

NettyのEpollは、aarch64アーキテクチャをサポートするLinux環境に実装されています。aarch64 Linux環境では、Netty Epoll APIを使用すると、NettyNIOAPIと比較してパフォーマンスを向上させることができます。 4.1.50.Final 

参考:https://stackoverflow.com/a/23465481/7913731

5.1.0および5.0.0ShardingSphere-ProxyTPC-Cパフォーマンステストの比較

TPC-Cを使用してShardingSphere-Proxyのベンチマークを行い、パフォーマンスの最適化の結果を検証します。以前のバージョンのShardingSphere-ProxyではPostgreSQLのサポートが制限されているため、TPC-Cテストを実行できません。そのため、バージョン5.0.0と5.1.0を比較に使用します。

ShardingSphere-Proxy自体のパフォーマンスの低下を強調するために、このテストでは、データシャーディング(1シャード)を備えたShardingSphere-Proxyを使用して、PostgreSQL14.2と比較します。

テストは、公式ドキュメントの「BenchmarkSQL Performance Test( https://shardingsphere.apache.org/document/current/cn/reference/test/performance-test/benchmarksql-test/ )」と構成に従って実行されます。 4スライスから1シャードに減少します。

テスト環境

テストパラメータ

BenchmarkSQLパラメーター:

  • ウェアハウス=192(データ量)
  • terminals = 192(同時実行数)
  • terminalWarehouseFixed = false
  • 実行時間30分

PostgreSQL JDBCパラメーター:

  • defaultRowFetchSize = 50
  • reWriteBatchedInserts = true

ShardingSphere-プロキシJVMパーツパラメータ:

  • -Xmx16g
  • -Xms16g
  • -Xmn12g
  • -XX:AutoBoxCacheMax = 4096
  • -XX:+ UseNUMA
  • -XX:+ DisableExplicitGC
  • -XX:LargePageSizeInBytes = 128m
  • -XX:+ SegmentedCodeCache
  • -XX:+ AggressiveHeap

試験結果

このホワイトペーパーのコンテキストとシナリオで得られた結論:

  • ShardingSphere-Proxy 5.0.0 + PostgreSQLに基づくと、5.1.0のパフォーマンスは約26.8%向上します。
  • ShardingSphere-Proxy 5.1.0は、PostgreSQLへの直接接続をベンチマークとして、損失を5.0.0と比較して約15%削減し、42.7%から27.4%に削減します。

コードの詳細はShardingSphereの各モジュール全体で最適化されているため、上記のテスト結果はすべての最適化ポイントを網羅しているわけではありません。

パフォーマンスの問題を表示する方法

時々、誰かが「ShardingSphereのパフォーマンスはどうですか?損失はいくらですか?」と尋ねることがあります。

私の意見では、パフォーマンスはニーズを満たすことができます。パフォーマンスは複雑な問題であり、多くの要因の影響を受けます。さまざまな環境とシナリオでは、ShardingSphereのパフォーマンスの低下は1%未満または50%に達する可能性があります。環境とシナリオがなければ、答えを出すことはできません。さらに、ShardingSphereのインフラストラクチャとして、そのパフォーマンスは研究開発プロセスにおける重要な考慮事項の1つです。ShardingSphereコミュニティのチームと個人は、ShardingSphereのパフォーマンスを極限まで高めるために、引き続き職人技を発揮します。

{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/5137513/blog/5481410
おすすめ