Search
Write a publication
Pull to refresh
45
15
Сергей Цыпанов @tsypanov

Разработчик

Send message
США свою систему ПРО (из договора о которой они вышли в одностороннем порядке ещё в 2002 году) почему-то не сворачивают и не ликвидируют. Более того, ПРО размещается в непосредственной близости от границ России, в частности в Польше и Румынии. Официально заявляется, что это для защиты от Ирана (верим!). И вот Минобороны России вполне резонно подозревает, что пусковые установки ракет ПРО в этих странах могут использоваться в т. ч. для размещения наступательного вооружения и запрашивает проверку этих установок. На что получает отказ, хотя если наступательных вооружений там нет и установка их невозможна, то этот отказ выглядит подозрительным.

Получается, что Россия не может запросить даже проверку объектов, вызывающих у неё опасение (небезосновательно), при этом от самой России в ультимативной форме требуют уничтожить вооружение, которое, во-первых, не размещается непосредственно у границ США, и во-вторых не является доказанным нарушением Договора РСМД.

Вы правы, передача размера в StringBuilder даёт неплохой прирост в данном случае


@Benchmark
public String appendBoundsSized(Data data) {
  int beginIndex = data.beginIndex;
  int endIndex = data.endIndex;

  return new StringBuilder(endIndex - beginIndex + 2)
          .append('L')
          .append(data.str, beginIndex, endIndex)
          .append(';')
          .toString();
}

Вывод


Benchmark                             length nonLatin   Score   rror Units
appendBounds                              10     true    41,3 ±  0,9 ns/op
appendBounds                             100     true   143,6 ±  8,1 ns/op
appendBounds                            1000     true  1206,5 ± 48,7 ns/op

appendBoundsSized                         10     true    42,6 ±  0,7 ns/op
appendBoundsSized                        100     true   116,2 ± 17,1 ns/op
appendBoundsSized                       1000     true   880,9 ± 33,7 ns/op

appendBounds                              10    false    28,4 ±  0,2 ns/op
appendBounds                             100    false    99,0 ±  3,9 ns/op
appendBounds                            1000    false   663,3 ± 44,5 ns/op

appendBoundsSized                         10    false    29,5 ±  0,9 ns/op
appendBoundsSized                        100    false    68,7 ±  3,9 ns/op
appendBoundsSized                       1000    false   485,6 ± 11,2 ns/op

appendBounds:·gc.alloc.rate.norm          10     true   200,0 ±  0,0  B/op
appendBounds:·gc.alloc.rate.norm         100     true  1192,0 ±  0,0  B/op
appendBounds:·gc.alloc.rate.norm        1000     true 10200,0 ±  0,0  B/op

appendBoundsSized:·gc.alloc.rate.norm     10     true   192,0 ±  0,0  B/op
appendBoundsSized:·gc.alloc.rate.norm    100     true   736,0 ±  0,0  B/op
appendBoundsSized:·gc.alloc.rate.norm   1000     true  6144,0 ±  0,0  B/op

appendBounds:·gc.alloc.rate.norm          10    false   112,0 ±  0,0  B/op
appendBounds:·gc.alloc.rate.norm         100    false   544,0 ±  0,0  B/op
appendBounds:·gc.alloc.rate.norm        1000    false  4152,0 ±  0,0  B/op

appendBoundsSized:·gc.alloc.rate.norm     10    false   112,0 ±  0,0  B/op
appendBoundsSized:·gc.alloc.rate.norm    100    false   288,0 ±  0,0  B/op
appendBoundsSized:·gc.alloc.rate.norm   1000    false  2096,0 ±  0,0  B/op

Интересно, что в этом случае (малое количество вызовов StringBuilder.append) точное выделение памяти даёт очень хороший прирост, чего не скажешь о случае, когда вызовов StringBuilder.append значительно больше.

Спасибо! Уже не первый год работаю, но не устаю удивляться, как много тонкостей стоит вроде бы за простым кодом.

Вы имеете ввиду неэквивалентность байт-кода? Поведение обоих методов, насколько я понимаю, одинаковое:


String foo(String a1, String a2, String a3) {
  StringBuilder sb = new StringBuilder();
  sb.append(a1);
  sb.append(a2);
  sb.append(a3);
  return sb.toString();
}

String _foo(String a1, String a2, String a3) {
  return new StringBuilder()
          .append(a1)
          .append(a2)
          .append(a3)
          .toString();
}

Действительно, передав размер конечной строки сразу в конструктор StringBuilder-а можно выделить память только один раз и ровно столько, сколько нужно. Но это почти не даёт прироста.
Представьте такой код:


public class ToHexStringConverter {

  private static final char[] HEX_CHARS = {
          '0', '1', '2', '3',
          '4', '5', '6', '7',
          '8', '9', 'A', 'B',
          'C', 'D', 'E', 'F'
  };

  public String toHexString(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (byte b : bytes) {
      int temp = (int) b & 0xFF;
      sb.append(HEX_CHARS[temp / 16]);
      sb.append(HEX_CHARS[temp % 16]);
    }
    return sb.toString();
  }

  public String patched_toHexString(byte[] bytes) {
    StringBuilder sb = new StringBuilder(bytes.length * 2);
    for (byte b : bytes) {
      int temp = (int) b & 0xFF;
      sb.append(HEX_CHARS[temp / 16]);
      sb.append(HEX_CHARS[temp % 16]);
    }
    return sb.toString();
  }
}

Здесь оба метода преобразовывают входной массив байт в его шестнадцатеричное представление. Первый метод исходный, второй — улучшенный. Смысл улучшения в том, что мы используем известный размер массива, а также тот факт, что каждый байт соответствует двум знакам, добавляемым к StringBuilder-у, для передачи ёмкости в конструктор.


Но увы, это не даёт значимого прироста. Возьмём 20 Мб и скормим обоим методам:


@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(jvmArgsAppend = {"-XX:+UseParallelGC", "-Xms2g", "-Xmx2g"})
public class SizedStringBuilderBenchmark {

  private byte[] bytes;
  private ToHexStringConverter converter;

  @Setup
  public void init() {
    bytes = new byte[1024 * 1024 * 20];
    converter = new ToHexStringConverter();
    ThreadLocalRandom.current().nextBytes(bytes);
  }

  @Benchmark
  public String original() {
    return converter.toHexString(bytes);
  }

  @Benchmark
  public String patched() {
    return converter.patched_toHexString(bytes);
  }
}

На выходе имеем


Benchmark                        Mode  Cnt          Score   Error   Units

original                         avgt   25        124,766 ± 1,610   ms/op
patched                          avgt   25        113,763 ± 3,432   ms/op

original:·gc.alloc.rate.norm     avgt   25  192938425,434 ± 0,886    B/op
patched:·gc.alloc.rate.norm      avgt   25   83886183,845 ± 1,341    B/op

Действительно, выигрыш по памяти более чем двукратный, но разница во времени не столь велика.


Конкретно в этом случае ощутимый прирост даст выбрасывание StringBuilder-a:


public class ToHexStringConverter {

  private static final char[] HEX_CHARS = {
          '0', '1', '2', '3',
          '4', '5', '6', '7',
          '8', '9', 'A', 'B',
          'C', 'D', 'E', 'F'
  };

  //...

  public String chars_toHexString(byte[] bytes) {
    char[] result = new char[bytes.length * 2];
    int idx = 0;
    for (byte b : bytes) {
      int temp = (int) b & 0xFF;
      result[idx++] = HEX_CHARS[temp / 16];
      result[idx++] = HEX_CHARS[temp % 16];
    }
    return new String(result);
  }
}

Берём тот же замер:


@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(jvmArgsAppend = {"-XX:+UseParallelGC", "-Xms2g", "-Xmx2g"})
public class SizedStringBuilderBenchmark {

  private byte[] bytes;
  private ToHexStringConverter converter;

  @Setup
  public void init() {
    bytes = new byte[1024 * 1024 * 20];
    converter = new ToHexStringConverter();
    ThreadLocalRandom.current().nextBytes(bytes);
  }

  @Benchmark
  public String original() {
    return converter.toHexString(bytes);
  }

  @Benchmark
  public String patched() {
    return converter.patched_toHexString(bytes);
  }

  @Benchmark
  public String chars() {
    return converter.chars_toHexString(bytes);
  }

}

И вот тут получаем почти 4-х кратный прирост по времени:


Benchmark                        Mode  Cnt          Score   Error   Units

original                         avgt   25        124,766 ± 1,610   ms/op
patched                          avgt   25        113,763 ± 3,432   ms/op
chars                            avgt   25         32,367 ± 0,656   ms/op

original:·gc.alloc.rate.norm     avgt   25  192938425,434 ± 0,886    B/op
patched:·gc.alloc.rate.norm      avgt   25   83886183,845 ± 1,341    B/op
chars:·gc.alloc.rate.norm        avgt   25  125829182,781 ± 0,242    B/op

Спасибо за ссылку, я упустил эту особенность. Интересно, почему тогда javac не превращает


StringBuilder sb = new StringBuilder();
sb.append(a1);
sb.append(a2);
sb.append(a3);
sb.toString();

в


new StringBuilder().append(a1).append(a2).append(a3).toString();

ещё на этапе компиляции исходного кода? Что мешает такому преобразованию?

Добрый день, действительно ли использование объектных пулов даёт в вашем проекте ощутимый прирост производительности? Вопрос задаю в связи с всплывшим в памяти докладом Алексея Кудрявцева "Computer Science ещё жива", в котором утверждается, что от объектных пулов он отказался после переезда на "восьмёрку".


Вот тут этот момент в докладе: https://youtu.be/Ra2RSsyO4XU?t=2097

Спасибо вам за ссылку! Попробую копнуть в этом направлении.

по зомбоящику РосПропаганда ему круглосуточно рассказывает

Вы-то "зомбоящик", насколько я могу судить, не смотрите. А коль так, то откуда вы знаете, что он рассказывает, да ещё и круглосуточно?


не рвал дупу

Что такое "дупа"?


перевода стрелок как дети

Именно это вы и сделали, написав, что пользователи, не восторгающиеся шутками про "глюпи рюзге вспарывает обшивку своего окрабля, азаза", являются ольгинскими (иначе же и быть не может)


Бутина уже заговорила

Да-да, #скоро :)


в России начался настоящий патриотический угар

Судя по использованию слов вроде "дупа" вы живёте не в России. Откуда вы тогда знаете какой угар там начался? ТСН рассказала? ;)


пропогандонш, непричемышей

Ещё раз перечитайте мой предыдущий ответ про предсказуемость.

насколько же вы предсказуемые

ольгинцам

зрителям зомбоящика

РосПропаганда

агент Трамп

Вы-то, я вижу, ни разу не предсказуемы.

Процентов 95 уже через месяц не вспомнят об этом событии, что не отменяет формирования картины мира из подобных «Не более» и «очередной мемасик».

Опять же, если отмотать твиттеры этих самых журналистов до эпизода с плавающей ступенью и сравнить отзывы тогда и сейчас, то видна вполне ясная разница в тональности высказываний и оценок.

Думаю, уместно задать этот вопрос "Роскосмосу". На мой взгляд, мешало наплевательское отношение к своему образу. Мой ответ был не об этом, а об освещении работы космонавтов "непредвзятыми" журналистами. Комментарии вроде


Это, вероятно, является ещё одним первым достижением российской космической программы: космонавт только что обнажил большой нож и проткнул им свой космический корабль.

и


Я отвлеклась на русских, распарывающих свой космический корабль

являются пертосянством чистой воды.

Окажись в этом же положении астронавты НАСА, эти же журналисты захлёбывались бы от хвалебных комментариев и подчёркивали бы важность, нужность и т. д. Но на их месте русские, поэтому объективные западные журналисты натужно шутят и соревнуются в петросянстве.
По поводу двойственного ощущения я ниже оставил комментарий про `SimpleJpaRepository::findAll`.

По поводу дальнейшего использования, я тут посмотрел внутрь org.springframework.data.jpa.repository.support.SimpleJpaRepository, там есть такой метод


public List<T> findAll(Iterable<ID> ids) {

    if (ids == null || !ids.iterator().hasNext()) {
        return Collections.emptyList();
    }

    if (entityInformation.hasCompositeId()) {

        List<T> results = new ArrayList<T>();

        for (ID id : ids) {
            results.add(findOne(id));
        }

        return results;
    }

    ByIdsSpecification<T> specification = new ByIdsSpecification<T>(entityInformation);
    TypedQuery<T> query = getQuery(specification, (Sort) null);

    return query.setParameter(specification.parameter, ids).getResultList();
}

В текущем виде возможны два случая, когда возвращается пустой список: 1) на вход пришла пустая коллекция ключей и 2) запрос вернул пустую выборку.


Так вот в первом случае возвращается неизменяемый список, а во втором — ArrayList.

Про чистый JDBC не скажу, а Hibernate возвращает пустой список (ЕМНИП, пустой ArrayList).

Тоже вариант, только в этом случае orElseGet не даёт преимущества перед orElse, т. к. Collections.emptyList() возвращает кэшированный список.

В Spring Data запросы вида


@Query("select u from UserEntity u where u.group.id = :groupId")
List<UserEntity> findUsersByGroup(@Param("groupId") Long groupId);

возвращают пустой список (ArrayList) для пустой выборки.

bystr1k прав, этот пример про инварианты. Далеко не все из них обнаруживаются статическими анализаторами.
Разумеется, это всё было найдено и исправлено именно в ходе код вычитки.

Information

Rating
889-th
Registered
Activity