最適化、最適化、再最適化

サーバーのパフォーマンス要件は文書化されており、特定のレベルを下回ることはできないため、サービスの義務により、プロファイラーを定期的に使用する必要があります。 いくつかの明白なアーキテクチャの変更とソリューションに加えて、多くの場合、モジュール間、プロジェクト間で重複する場所があり、共有する仮想マシンに追加の負荷を作成します。

Dateを操作するためのコードが最もよく出くわしたのは、それから開始するためです。



日付


12回以上、ユーザーからの1つのリクエストの処理中に、新しい日付オブジェクトが複数の異なる場所でどのように作成されるかを観察する機会がありました。 ほとんどの場合、目標は同じです-現在の時刻を取得します。 最も単純な場合、次のようになります。



public boolean isValid(Date start, Date end) { Date now = new Date(); return start.before(now) && end.after(now); }
      
      





それは見えるだろう-完全に明白で正しい解決策。 原則として、はい、2つの点を除きます。



  public boolean isValid(Date start, Date end) { long now = System.currentTimeMillis(); return start.getTime() < now && now < end.getTIme(); }
      
      







SimpleDateFormat


Webプロジェクトでは、文字列を日付に、または日付を文字列に変換するというタスクが頻繁に発生します。 タスクは非常に典型的で、ほとんどの場合次のようになります。



  return new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z").parse(dateString);
      
      





これは正確で迅速な解決策ですが、サーバーが数百のスレッドのそれぞれでユーザー要求ごとに文字列を解析する必要がある場合、かなり重いコンストラクターSimpleDateFormatによりサーバーのパフォーマンスに大きく影響し、フォーマッター自体に加えて、他の多くのオブジェクトが作成されます軽量カレンダー(サイズは400バイト以上)。



SimpleDateFormatを静的フィールドにすることで状況を簡単に解決できますが、スレッドセーフではありません。 また、競争の激しい環境では、NumberFormatExceptionを簡単にキャッチできます。



2番目の考え方は、同期を使用することです。 しかし、これはまだかなり疑わしいものです。 スレッド間の競争が激しい場合、パフォーマンスを改善できるだけでなく、悪化させることもできます。



しかし、少なくとも2つの解決策があります。





ランダム


私のプロジェクトでは、ランダムなエンティティをユーザーに返すというタスクがしばしば発生します。 通常、この種のコードは次のようになります。



  return items.get(new Random().nextInt(items.size()));
      
      





素晴らしい、簡単、速い。 ただし、メソッドへの呼び出しが多数ある場合、これは新しいRandomオブジェクトの継続的な作成を意味します。 簡単に回避できること:



  private static final Random rand = new Random(); ... return items.get(rand.nextInt(items.size()));
      
      





ここにあるように思えます-完全なソリューションですが、ここではそれほど単純ではありません。 Randomはスレッドセーフですが、マルチスレッド環境では動作が遅くなる可能性があります。 ただし、 Sun Oracleはすでにこれを処理しています。



  return items.get(ThreadLocalRandom.current().nextInt(items.size()));
      
      





ドキュメントに記載されているように-これは私たちのタスクに最適なソリューションです。 ThreadLocalRandomは 、マルチスレッド環境でRandomよりもはるかに効率的です。 残念ながら、このクラスはバージョン7以降でのみ使用可能です( バグ修正後、こんにちはTheShade )。 本質的に、このソリューションはSimpleDateFormatと同じですが、独自の個人クラスのみがあります。



ヌルではない


多くの開発者は、null値を避けて、次のように記述します。



 public Item someMethod() { Item item = new Item(); //some logic if (something) { fillItem(item); } return item; }
      
      





その結果、何かが真にならない場合でも、膨大な数のオブジェクトが作成されます(メソッドが頻繁に呼び出される場合)。



正規表現


Webアプリケーションを作成している場合、ほぼ確実に正規表現を使用しています。 典型的なコード:



 public Item isValid(String ip) { Pattern pattern = Pattern.compile("xxx"); Matcher matcher = pattern.matcher(ip); return matcher.matches(); }
      
      





最初の場合と同様に、新しいIPアドレスが到着するとすぐに、検証を行う必要があります。 呼び出しごとに-新しいオブジェクトのパック。 この特定のケースでは、コードをわずかに最適化できます。



 private static final Pattern pattern = Pattern.compile("xxx"); public Item isValid(String ip) { Matcher matcher = pattern.matcher(ip); return matcher.matches(); }
      
      





メソッド外で仲人を作成することも理想的ですが、残念ながらスレッドセーフではないため、常に作成する必要があります。 シングルスレッド環境に関しては、解決策があります。



 private static final Pattern pattern = Pattern.compile("xxx"); private final Matcher matcher = pattern.matcher(""); public Item isValid(String ip) { matcher.reset(ip); return matcher.matches(); }
      
      





最適なのは…そう、ThreadLocal。



日付を切り捨てる


別のかなり一般的なタスクは、時間、日、週で日付をトリミングすることです。 Apachevsky DateUtilsから独自のバイクまで、これを行うには非常に多くの方法があります。



  public static Date truncateToHours(Date date) { Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); }
      
      





たとえば、最近、hadoipフェーズのマップコードを分析すると、CPUの60%を消費する次の2行のコードに遭遇しました。



 key.setDeliveredDateContent(truncateToHours(byPeriodKey.getContentTimestamp())); key.setDeliveredDateAd(truncateToHours(byPeriodKey.getAdTimestamp()));
      
      





私にとっては大きな驚きでしたが、プロファイラーは嘘をついていません。 幸い、mapメソッドはスレッドセーフであることがわかり、カレンダーオブジェクトの作成はtruncateToHours()メソッドの外で正常に削除されました。 これにより、マップメソッドの速度が2倍になりました。



HashCodeBuilder


理由はわかりませんが、一部の開発者はapachevskieヘルパークラスを使用してhashcode()およびequals()メソッドを生成します。 以下に例を示します。



  @Override public boolean equals(Object obj) { EqualsBuilder equalsBuilder = new EqualsBuilder(); equalsBuilder.append(id, otherKey.getId()); ... } @Override public int hashCode() { HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(); hashCodeBuilder.append(id); ... }
      
      







もちろん、これは、アプリケーションの存続期間中にこれらのメソッドを数回使用する場合は悪くありません。 ただし、たとえば、hadoopジョブのソートフェーズ中に各キーに対して常に呼び出される場合、実行速度に大きな影響を与える可能性があります。



おわりに


なぜ私は-いいえ、私は決していくつかのオブジェクトの作成を節約するためにコードを実行してシャベルで駆り立てる衝動はありません。これは検討のための情報であり、これは誰かにとって非常に役立つでしょう。 読んでくれてありがとう。



All Articles