Статьи

Кросс-языковой бенчмаркинг, часть 3 — Подмодули Git и кросс-языковой тест для одной команды

В моих недавних постах в блоге ( часть 1часть 2 ) я подробно описал, как выполнить микропроцессор для  Java  и  C / C ++  с  JMH  и  Hayai . Я представил общий подход к исполнению на основе  Gradle .

Сегодня я хочу  улучшить  общую  структуру проекта . В прошлый раз я уже упоминал, что структура проектов Gradle не оптимальна. В первой части я в общих чертах повторю основную цель и результаты предыдущих статей, во-вторых представлю некоторые новые требования и, наконец, представлю вам более гибкую структуру модулей для разделения производственного кода и тестов, которая затем будет встроена в кросс языковой супер-проект.

1.) Что мы рассмотрели до сих пор

Чтобы отслеживать наш список Todo, вот выдержка из  части 1 :

Предположим, у нас есть проект на Java и C ++, в котором реализованы одни и те же алгоритмы. Мы хотим сравнить и сравнить производительность горячих частей кода. Мы также хотим отслеживать изменения во времени, когда исходный код проекта растет и изменяется. Мы будем:


  1. Сравнительный анализ Java-кода с JMH как часть сборки Gradle

  2. Сравнительный код C ++ с Hayai

  3. Интеграция бинарной компиляции c ++ с Gradle

  4. Интеграция исполнения Hayai с Gradle
  5. Объедините проекты Java и C ++ в одну межязыковую цепочку сборки Gradle
  6. Совокупные результаты JMH и Hayai дают третий сложный результат
  7. Разделить код тестирования из проектов в отдельный проект и выделенный SCM.
  8. Автоматически отправлять агрегированные результаты тестирования в хитро структурированный репозиторий git, чтобы отслеживать текущие версии исходного кода и результаты тестирования.

Мы уже достигли всех вычеркнутых предметов. Сегодня мы сосредоточимся на синих предметах.


2.) Грязные вещи

Для полноты картины   приведем скрипт сборки Gradle, в котором мы хотим определить новые требования сегодня:

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'me.champeau.gradle:jmh-gradle-plugin:0.2.0'
  }
}

repositories {
  mavenCentral()
  jcenter()
}

apply plugin: 'me.champeau.gradle.jmh'
apply plugin: 'java'

group = 'net.chroma'
version = '0.0.1-SNAPSHOT'

jmh {
  jmhVersion = '1.6.1'
}

sourceCompatibility = 1.8

dependencies {
  testCompile 'junit:junit:4.11'
}

Исходные данные:

  • Этот сценарий сборки отвечает за сборку тестируемого артефакта и за выполнение микро-бенчмаркинга.

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

Чтобы еще раз взглянуть на настройку проекта, перейдите по ссылке на ветку, с которой мы сегодня начинаем:  Chroma @ github, Branch: crolabefra_starting_point .

Требования:

  • Код бенчмаркинга должен находиться в отдельном проекте, который зависит от артефакта для бенчмарка, но сам артефакт должен быть независим от каких-либо библиотек бенчмаркинга или установки
  • Инструмент автоматизации должен быть вызван один раз для выполнения всех тестов по всем прикрепленным проектам.
  • Как разработчик мультиязычных тестов для разных языков, я хочу применить  плагин фреймворка , который обрабатывает все настройки, необходимые для получения данных результатов (в правильном формате).

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

  • использовать  git и подмодули  для разделения кода продукта и тестов
  • В целом картина выглядит  взаимосвязанной с интеграцией Hayai  в проекте по сравнительному анализу языка для кода Java и C / C ++.
  • связать все вместе в аккуратный  плагин Gradle  (будущий пост)

3.) Отдельный проект ориентиров

Это статус-кво из статьи 1 :

chromarenderer-java
├── src
    ├── main
      ├── java
    ├── jmh
      ├── java
    ├── test
      ├── java
├── build.gradle

Мы стремимся к следующей структуре:

├── chromarenderer-java-benchmarks
    ├── src
      ├── jmh
      ├── java
    ├── build.gradle
    ├── settings.gradle [new]
    ├── chromarenderer-java
        ├── src
            ├── main
                ├── java
            ├── test
                ├── java
        ├── build.gradle 

Преимущества очевидны:

  • У нас есть два разных модуля Gradle, которые разделяют зависимости сборки и библиотеки: Разделение зависимостей, этапы сборки и пользовательский код Gradle.
  • Код JMH не является частью источника продукта: разделение ответственности, видимость. Более чистая настройка проекта. Код для бенчмарка может быть зависимостью для извлечения из репозитория или библиотеки, или от того, что вам приходит в голову!
  • В разных репозиториях git можно управлять двумя разными каталогами, значит, тестовый код и версии производственного кода больше не связаны друг с другом! Набор тестов может быть легко выполнен на разных версиях производственного кода SCM, просто проверив еще один коммит!

Единственный недостаток, который приходит с первым пунктом, заключается в том, что мы оказываемся в многомодульном проекте. Но настройка очень проста в Gradle. В основном требуется только файл settings.gradle в каталоге ‘chromarenderer-java-benchmarks’:

include 'chromarenderer-java'
include 'chromarenderer-java-benchmarks'

После изменения структуры каталогов, мы можем продолжить удаление частей из обоих файлов build.gradle, которые нам больше не нужны. Для проекта бенчмаркинга сначала:

buildscript {
  repositories {
  jcenter()
  }
  dependencies {
  classpath 'me.champeau.gradle:jmh-gradle-plugin:0.2.0'
  }
}

repositories {
  mavenCentral()
}

apply plugin: 'me.champeau.gradle.jmh'
apply plugin: 'java'

group = 'net.chroma'
version = '0.0.1-SNAPSHOT'

jmh {
  jmhVersion = '1.6.1'
}

sourceCompatibility = 1.8

dependencies {
-  testCompile 'junit:junit:4.11'
+  compile project(":chromarenderer-java)
}

Хорошо, нам больше не нужна зависимость JUnit, но теперь нам нужна зависимость от проекта для сравнения. Ну, мы не так много выиграли. Но подождите, что происходит с проектом производственного кода? Смотреть:

- buildscript {
-  repositories {
-  jcenter()
-  }
-  dependencies {
-  classpath 'me.champeau.gradle:jmh-gradle-plugin:0.2.0'
-  }
-}

repositories {
  mavenCentral()
-  jcenter()
}

-apply plugin: 'me.champeau.gradle.jmh'
apply plugin: 'java'

group = 'net.chroma'
version = '0.0.1-SNAPSHOT'

-jmh {
-  jmhVersion = '1.6.1'
-}

sourceCompatibility = 1.8

dependencies {
  testCompile 'junit:junit:4.11'
}

Вот что мы хотели увидеть! Ни единого доказательства того, что JMH проводит тесты по проекту! Как в ванильном проекте Gradle. Для эффективных примеров взгляните на примеры репозиториев проектов, которые я подготовил для этой статьи:


 4.) Подмодули Git

Теперь для всех вас, кто никогда раньше не использовал подмодули Git, вот краткое резюме: Грубо говоря, подмодули Git становятся удобными, когда вы хотите иметь

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

4.1) Понятие подмодулей

Пример использования:  предположим, у вас есть два проекта, в обоих из которых используется общий набор файлов:

PROJECT_A (own git repository)
├── src
  ├── main
  ├── java
├── common_assets
    ├── ....

PROJECT_B (own git repository)
├── src
  ├── main
  ├── groovy
├── common_assets
    ├── ...

Теперь вместо добавления одного и того же набора файлов в оба репозитория git (что, очевидно, приведет к путанице в версии, как только оба проекта захотят внести изменения в общие файлы, которые затем нужно будет синхронизировать вручную), будет более разумно перемещать все файлы в ‘common_assets’ в свой собственный репозиторий git:

PROJECT_A (git repository A)
├── src
  ├── main
  ├── java
├── common_assets (git repository C)
  ├── ....

PROJECT_B (git repository B)
├── src
  ├── main
  ├── groovy
├── common_assets (git repository C)
  ├── ...

Теперь вы можете указать вашему репозиторию проекта клонировать другой репозиторий в каталог и пометить этот каталог как субмодуль.

$> git submodule add git@github.com:bensteinert/repository-C.git

Затем Git клонирует этот субмодульный репозиторий, который останется полностью независимым от окружающего репозитория.

То, что теперь может вызывать головные боли, заключается в том, что родительский репозиторий отслеживает только текущую версию HEAD. Этапы и наборы изменений в файлах внутри подмодуля не видны родителю, распознаются только изменения локальной версии HEAD. Первоначально, после команды выше, набор изменений должен выглядеть следующим образом:

$> git status
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

   new file:  .gitmodules
   new file:  common_assets

.gitmodules содержит информацию о недавно созданном подмодуле. Новый каталог common_assets теперь отображается в виде простого файла. Фоном было то, что git создал ссылку на каталог внутри папки .git (точнее, .git / modules / common_assets). После создания коммита из набора изменений git сохранил текущую извлеченную ревизию в подмодуле. Просто чтобы внести в него, возможно, больше ясности, войдите в подмодуль и измените ГОЛОВУ, проверив другой коммит. Эта операция создаст набор изменений, который будет выглядеть следующим образом

$> git status
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

   modified:  common_assets (new commits)

Если вы вернетесь к предыдущему коммиту, изменения исчезнут. Эти изменения подмодуля HEAD могут быть добавлены в коммит, как и любое другое изменение файла. Единственное, что будет храниться внутри родительского репозитория, — это идентификатор фиксации текущего извлеченного HEAD. За тем, что происходит за кулисами в родительском каталоге, можно легко наблюдать с помощью команды ‘diff’ после повторного изменения версии подмодуля HEAD:

$> git diff
diff --git a/common_assets b/common_assets
index 244411c..d416be2 160000
--- a/common_assets
+++ b/common_assets
@@ -1 +1 @@
-Subproject commit 244411c0aad0b2278eb05622966ba59e1f48ab4b
+Subproject commit d416be2c15250a85bf7b993c0c4a41ff99162b56

Это снова выглядит как обычное изменение файла, но на самом деле вы изменили отслеживаемую версию HEAD подмодуля :).

Надеюсь, я внес некоторую ясность в концепцию. В любом случае я рекомендую руководство по git для дальнейшего чтения .

Общие преимущества

  • Подмодуль позволяет объединять разные репозитории в одном родителе, не добавляя исходные файлы во второй SCM.
  • Изменения в субмодульном контенте могут быть сделаны в любое время, потому что это обычный git-репозиторий сам по себе.
  • Вы можете решить, когда вытащить новый контент в ваши подмодули.
  • Проект, который состоит из различных подмодулей, может отслеживать различные комбинации, которые были зафиксированы в истории.
  • Версии подмодуля HEAD могут быть легко изменены в любой момент в произвольной точке истории, не вызывая существенных изменений в наборе файлов.

4.2) Git подмодули и тесты?

Давайте вернемся к нашей новой структуре проекта. Объединение новой структуры с наличием тестов, «окружающих» производственный код, позволяет нам размещать их в независимых репозиториях. Затем проект для эталонного тестирования извлекается как субмодуль в эталонный проект:

├── chromarenderer-java-benchmarks ( git repository chromarenderer-java-benchmarks)
    ├── src
      ├── jmh
      ├── java
    ├── build.gradle
    ├── settings.gradle
    ├── chromarenderer-java (git submodule chromarenderer-java)
        ├── src
            ├── main
                ├── java
            ├── test
                ├── java
        ├── build.gradle 

Важное замечание относительно клонирования репозитория git, которое содержит подмодули:

После клонирования репозитория git все содержащиеся в нем подмодули НЕ инициализируются и автоматически клонируются! У вас есть два варианта. Либо клонируйте репозиторий с рекурсивным параметром:

$> git clone --recursive git@github.com:bensteinert/chromarenderer-java-benchmarks.git

или, после обычного клона, выполните:

$> git submodule update --init

Все подмодули, имеющиеся в хранилище, будут выбраны, а текущая действующая версия HEAD будет извлечена.


5.) Отсутствующий «кросс» в межъязыковом бенчмаркинге

Мы говорили о части Java до сих пор. Понятия могут быть непосредственно применены к проекту C ++, который мы начали также во второй части, конечно. Чтобы не беспокоить вас одними и теми же вещами дважды, я просто сошлюсь на репозиторий git, подготовленный для проекта тестов Hayai : chromarenderer-cpp-benchmarks @ github . Вы узнаете точно такие же изменения:

  • Все связанные с тестами вещи переместились на один каталог вверх
  • Базовый проект chromarenderer-cpp избавился от всех зависимостей от инфраструктуры бенчмаркинга.
  • Окружающий модуль бенчмаркинга определяет зависимость от основного модуля

Круто, похоже, что это можно обобщить :).

Значит, теперь у нас есть два разных проекта бенчмаркинга, которые можно запускать изолированно

5.1) Еще один супер проект

Теперь мы можем проверить оба репозитория и выполнить тесты по одной команде. Но нет, мы еще более ленивы. Нам нужен один  кросс- языковой репозиторий с одной  кросс- языковой сборкой. Итак, давайте добавим еще один уровень супер-проекта:

CroLaBeFra (git repository crolabefra) [new]
├── settings.gradle [new]
├── chromarenderer-java-benchmarks (git submodule chromarenderer-java-benchmarks)
    ├── [src ...]
    ├── build.gradle
    ├── settings.gradle
    ├── chromarenderer-java (git submodule chromarenderer-java)
        ├── [src ...]
        ├── build.gradle 
├── chromarenderer-cpp-benchmarks ( git submodule chromarenderer-cpp-benchmarks)
    ├── [src ...]
    ├── build.gradle
    ├── settings.gradle
    ├── chromarenderer-cpp (git submodule chromarenderer-cpp)
        ├── [src ...]
        ├── build.gradle

5.2) Небольшая ловушка Gradle

Теоретически предлагаемая модель каталогов выглядит многообещающе. Но как только вы все перенесете и попробуете, вы удивитесь, что эта установка не работает. В вашем файле settings.gradle в супер-проекте CroLaBeFra вы, вероятно, попробуете что-то вроде:


1

2

3
rootProject.name = 'CroLaBeFra'
include 'chromarenderer-cpp-benchmarks'
include 'chromarenderer-java-benchmarks'

Теперь вас поразило то, что Gradle не может обнаружить несколько файлов «settings.gradle» в одном проекте. Следовательно, включения в подпроекты игнорируются. Но это не было бы Gradle, если бы не было обходного пути;). Поскольку супер проект должен знать, что он включает в себя больше подпроектов в сборку, вы можете включить файлы ‘settings.gradle’ в свой супер проект:

rootProject.name = 'CroLaBeFra'
include 'chromarenderer-cpp-benchmarks'
include 'chromarenderer-java-benchmarks'
apply from: 'chromarenderer-cpp-benchmarks/settings.gradle'
apply from: 'chromarenderer-java-benchmarks/settings.gradle

Теперь Gradle будет читать файлы подпроекта ‘settings.gradle’, а также, если он будет частью файла суперпроекта ‘settings.gradle’. Недостатком является то, что предполагаемая структура каталогов становится противоречивой. Задний план:

apply from: 'chromarenderer-java-benchmarks/settings.gradle'

в основном означает так же, как

include 'chromarenderer-java'

потому что контент будет просто оцениваться в контексте супер проекта. Но этот каталог и подпроект ‘chromarenderer-java’ не существует на уровне каталогов суперпроекта! Но опять же, это не было бы Gradle, если бы не было решения.

К счастью, Gradle сначала собирает все включения из проектов, прежде чем получить доступ к каталогам. Это означает, что мы можем изменить его позже, добавив следующие строки:

project(':chromarenderer').projectDir = new File(rootDir, 'chromarenderer-cpp-benchmarks/chromarenderer')
project(':chromarenderer-java').projectDir = new File(rootDir, 'chromarenderer-java-benchmarks/chromarenderer-java')

Это выглядит немного грязно, но, в конце концов, пока Gradle не поддерживает несколько файлов settings.gradle изначально, другого пути нет. Поскольку мы делаем все грязные вещи в супер-проекте, нам не нужно трогать наши подпроекты, и они остаются свободными от таких обходных путей ».

Окончательный результат дня со всеми внесенными изменениями и предложениями:  CroLaBeFra-POC @ github

Клонировать (рекурсивно) и просто запустить

$> gradle runBenchmarks jmh

Миссия выполнена 🙂

6.) Заключение и текущая работа

Сегодня мы решили тему 7 и 5 из списка. Наш основной проект продукта не зависит от какой-либо сравнительной инфраструктуры и / или исходного кода. Простая структура каталогов в сочетании с мощью подмодулей Git позволяет легко управлять многомодульной установкой Gradle. С некоторой магией Gradle мы добавили еще один уровень проекта сверху, который позволяет получить доступ и выполнить все задачи бенчмаркинга с помощью одной команды. Глядя на список, мы почти закончили.


  1. Сравнительный анализ Java-кода с JMH как часть сборки Gradle

  2. Сравнительный код C ++ с Hayai

  3. Интеграция бинарной компиляции c ++ с Gradle

  4. Интеграция исполнения Hayai с Gradle

  5. Объедините проекты Java и C ++ в одну межязыковую цепочку сборки Gradle
  6. Совокупные результаты JMH и Hayai дают третий сложный результат

  7. Разделить код тестирования из проектов в отдельный проект и выделенный SCM.
  8. Автоматически отправлять агрегированные результаты тестирования в хитро структурированный репозиторий git, чтобы отслеживать текущие версии исходного кода и результаты тестирования.

Но у меня есть еще одна тема, которую я хотел бы добавить в список:

  • Извлеките все настройки для бенчмаркинга из файлов build.gradle в простые в использовании плагины Gradle, чтобы предложить их всем вам  :). Общая цель состоит в том, чтобы иметь набор различных плагинов для разных языков, которые могут быть применены к проекту бенчмаркинга. В идеале единственное, что вам нужно сделать, это
apply plugin: 'com.comsysto.gradle.crolabefra.cpp'

вместо того, чтобы скопировать тридцать строк кода скрипта Gradle. Звучит хорошо? Следите за моей следующей статьей!

Пока!