在您的构建过程中添加微基准测试
介紹
作為一個行業,我們正在采用更高的透明度和更可預測的構建過程,以減少構建軟件的風險。 持續交付的核心原則之一是通過反饋循環收集反饋。 在Dev9中 ,我們采用了與CD原則相一致的“ 先知道 ”原則,這意味著我們(開發團隊)希望首先知道何時出現故障,性能下降或任何與之不符的結果。業務目標。
Maven和其他構建工具為開發人員提供了標準化的工具和生態系統,可在其中建立和交流反饋。 盡管單元測試,功能,構建驗收,數據庫遷移,性能測試和代碼分析工具已成為開發流程中的主要內容,但基準測試基本上仍然不在流程之外。 這可能是由于缺乏開源的,低成本的工具或輕量級的庫,這些庫增加了最小的復雜性。
現有工具通常需要將外部工具與運行時工件集成在一起,從而使復雜性更加復雜,并且測試未保存在同一源存儲庫中,甚至沒有存儲在源存儲庫中。 本地開發人員無法毫不費力地運行基準測試,因此測試會很快失去其價值。 除了主流的解決方案問題外,基準測試通常不是在課堂上講授的,并且通常在沒有必要的隔離才能獲得可靠結果的情況下實施。 這使得所有有關基準測試結果的博客或帖子成為巨魔的成熟目標。
綜上所述,圍繞代碼庫的關鍵區域進行某種基準覆蓋仍然很重要。 建立有關代碼關鍵部分的歷史知識可以幫助影響優化工作,向團隊通報技術欠債,在性能閾值更改已提交時發出警報并比較算法的先前版本或新版本。 現在的問題是,如何找到基準并輕松地將基準添加到我的新項目或現有項目中。 在此博客中,我們將專注于Java項目(1.7+)。 該示例代碼將利用Maven,盡管Gradle的工作原理非常相似。 我在整個博客中提出了一些建議,這些建議是基于過去項目的經驗得出的。
JHM簡介
在對基準Java代碼進行基準測試時,有很多強大的選擇,但是它們大多數都有缺點,包括許可證費用,額外的工具,字節代碼操縱和/或Java代理,使用非基于Java的代碼概述的測試以及高度復雜的配置設置。 我喜歡使測試盡可能接近被測代碼,以減少脆性,降低內聚力并減少耦合。 我認為我以前使用過的大多數基準測試解決方案太麻煩了,或者運行測試的代碼不夠孤立(完全集成在代碼中),或者包含在遠離源代碼的輔助解決方案中。
本博客的目的是演示如何在構建管道中添加輕量級基準測試工具,因此我將不詳細介紹如何使用JMH,以下博客是學習的絕佳資源:
- http://jmhwiki.blogspot.com
- http://java-performance.info/jmh/
- http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/
基準測試模式
關于模式和評分,我想指出一小部分,因為它們在基本配置的設置中起著重要作用。 在基本級別上,JMH有兩種主要的度量類型:吞吐量和基于時間的度量。
吞吐量測量
吞吐量是每單位時間可以完成的操作量。 隨著框架增加測試的負載量,JMH會維護成功和失敗操作的集合。 注意:確保方法或測試完全隔離,并且諸如測試對象創建之類的依賴項是在方法之外或在設置方法中進行預測試的。 使用吞吐量時,該值越高,越好,因為它表明可以在單位時間內運行更多的操作。
基于時間的測量
基于時間的測量是吞吐量的反伙伴。 基于時間的測量的目的是確定特定操作每單位時間運行多長時間。
平均時間
最常見的基于時間的度量是“ AverageTime”,用于計算操作的平均時間。 JMH還將產生“ 得分錯誤 ”,以幫助確定對產生得分的信心。 “ 得分誤差 ”通常是置信區間的1/2,表示結果與平均時間的偏離程度。 結果越低,越好,因為它表明每次操作的平均運行時間較短。
采樣時間
SampleTime與AverageTime相似,但是JMH嘗試增加更多的負載并查找會產生失敗百分比矩陣的失敗。 使用AverageTime時,數字越小越好,這些百分比對于確定由于吞吐量和時間長度而導致失敗的位置很有用。
SingleShotTime
最后也是最不常用的模式是SingleShotTime。 此模式實際上是一次運行,可用于冷測方法或測試您的測試。 如果在運行基準測試時作為參數傳遞SingleShotTime,可能會很有用,但會減少運行測試所需的時間(盡管這樣做會減少測試的價值并可能使它們自重)。 與其他基于時間的測量一樣,該值越低越好。
將JMH添加到Java項目
目標:本部分將展示如何創建可重復使用的工具,該工具可在不增加代碼開銷或代碼重復的情況下添加新測試。 注意,這些依賴項在“測試”范圍內,以避免將JMH添加到最終工件中。 我創建了一個在使用Protobuf替代REST for Microservices時使用JMH的github存儲庫。 可以在這里找到代碼: https : //github.com/mike-ensor/protobuf-serialization
1)首先將依賴項添加到項目中:
<dependencies> <!-- Other libraries left out for brevity --> <!-- jmh.version is the lastest version of JMH. Find by visitinghttp://search.maven.org --><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>${jmh.version}</version><scope>test</scope></dependency><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>${jmh.version}</version><scope>test</scope></dependency> <!-- Other libraries left out for brevity --> </dependencies>2)JMH建議將基準測試和工件包裝在同一uber jar中。 有幾種方法可以實現uber jar,顯式地使用made的“ shade”插件或隱式地使用Spring Boot,Dropwizard或具有類似結果的某些框架。 出于本博客文章的目的,我使用了Spring Boot應用程序。
3)添加具有主條目類和全局配置的測試工具。 在此步驟中,在項目的測試區域中創建一個入口點(用#1表示)。 目的是避免將基準代碼與主要工件打包在一起。
3.1)添加BenchmarkBase文件(在#2上方指示)。 此文件將用作基準測試的入口,并包含測試的所有全局配置。 我編寫的類正在尋找一個包含配置屬性的“ benchmark.properties”文件(上面在#3中指出)。 JMH可以選擇輸出文件結果,并且此配置是為JSON設置的。 結果與您的持續集成工具一起使用,可以(應該)存儲以供歷史使用。
此代碼段是Maven運行的Benchmark流程的基本工具和入口點(在下面的步驟5中進行設置)。此時,項目應該能夠運行基準測試,因此讓我們添加一個測試用例。
@SpringBootApplication public class BenchmarkBase {public static void main(String[] args) throws RunnerException, IOException {Properties properties = PropertiesLoaderUtils.loadAllProperties("benchmark.properties");int warmup = Integer.parseInt(properties.getProperty("benchmark.warmup.iterations", "5"));int iterations = Integer.parseInt(properties.getProperty("benchmark.test.iterations", "5"));int forks = Integer.parseInt(properties.getProperty("benchmark.test.forks", "1"));int threads = Integer.parseInt(properties.getProperty("benchmark.test.threads", "1"));String testClassRegExPattern = properties.getProperty("benchmark.global.testclassregexpattern", ".*Benchmark.*");String resultFilePrefix = properties.getProperty("benchmark.global.resultfileprefix", "jmh-");ResultFormatType resultsFileOutputType = ResultFormatType.JSON;Options opt = new OptionsBuilder().include(testClassRegExPattern).warmupIterations(warmup).measurementIterations(iterations).forks(forks).threads(threads).shouldDoGC(true).shouldFailOnError(true).resultFormat(resultsFileOutputType).result(buildResultsFileName(resultFilePrefix, resultsFileOutputType)).shouldFailOnError(true).jvmArgs("-server").build();new Runner(opt).run();}private static String buildResultsFileName(String resultFilePrefix, ResultFormatType resultType) {LocalDateTime date = LocalDateTime.now();DateTimeFormatter formatter = DateTimeFormatter.ofPattern("mm-dd-yyyy-hh-mm-ss");String suffix;switch (resultType) {case CSV:suffix = ".csv";break;case SCSV:// Semi-colon separated valuessuffix = ".scsv";break;case LATEX:suffix = ".tex";break;case JSON:default:suffix = ".json";break;}return String.format("target/%s%s%s", resultFilePrefix, date.format(formatter), suffix);}}4)創建一個類來對操作進行基準測試。 請記住,基準測試將針對整個方法主體進行,包括日志記錄,文件讀取,外部資源等。請注意要進行基準測試并減少或刪除依賴項,以便隔離主題代碼以確保對結果的信心更高。 在此示例中,在
@State(Scope.Benchmark) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) public class SerializationBenchmark {private RecipeService service;private Recipe recipe;private byte[] protoRecipe;private String recipeAsJSON;@Setup(Level.Trial)public void setup() {IngredientUsed jalepenoUsed = new IngredientUsed(new Ingredient("Jalepeno", "Spicy Pepper"), MeasurementType.ITEM, 1);IngredientUsed cheeseUsed = new IngredientUsed(new Ingredient("Cheese", "Creamy Cheese"), MeasurementType.OUNCE, 4);recipe = RecipeTestUtil.createRecipe("My Recipe", "Some spicy recipe using a few items", ImmutableList.of(jalepenoUsed, cheeseUsed));service = new RecipeService(new ObjectMapper());protoRecipe = service.recipeAsProto(recipe).toByteArray();recipeAsJSON = service.recipeAsJSON(recipe);}@Benchmarkpublic Messages.Recipe serialize_recipe_object_to_protobuf() {return service.recipeAsProto(recipe);}@Benchmarkpublic String serialize_recipe_object_to_JSON() {return service.recipeAsJSON(recipe);}@Benchmarkpublic Recipe deserialize_protobuf_to_recipe_object() {return service.getRecipe(protoRecipe);}@Benchmarkpublic Recipe deserialize_json_to_recipe_object() {return service.getRecipe(recipeAsJSON);}}標題:該要點是從Protobuf序列化中提取的示例基準測試用例
現在,當您執行測試jar時,所有* Benchmark * .java測試類都將運行,但這通常并不理想,因為該過程沒有隔離,并且對基準測試的時間和方式進行一定的控制對于保持構建時間很重要下。
讓我們構建一個Maven配置文件,以控制運行基準測試的時間以及可能啟動應用程序的時間。 注意,為了顯示Maven集成測試啟動/停止服務器的目的,我已將此內容包含在博客文章中。 我會警告需要啟動或停止應用程序服務器,因為這可能會帶來資源獲取(REST調用)的成本,而這并不是很孤立。
5)概念是創建一個Maven配置文件以獨立運行所有基準測試(即,沒有單元測試或功能測試)。 這將使基準測試可以與其余構建管道并行運行。 請注意,該代碼使用“ exec”插件并運行uber jar,以查找指向主類的完整類路徑路徑。 此外,可執行文件范圍僅限于“測試”源,以避免將基準代碼放入最終工件中。
<profile><id>benchmark</id><properties><maven.test.ITests>true</maven.test.ITests></properties><build><plugins><!-- Start application for benchmarks to test against --><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><executions><execution><id>pre-integration-test</id><goals><goal>start</goal></goals></execution><execution><id>post-integration-test</id><goals><goal>stop</goal></goals></execution></executions></plugin><!-- Turn off unit tests --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><configuration><excludes><exclude>**/*Tests.java</exclude><exclude>**/*Test.java</exclude></excludes></configuration></plugin><plugin><groupId>org.codehaus.mojo</groupId><artifactId>exec-maven-plugin</artifactId><version>1.5.0</version><executions><execution><goals><goal>exec</goal></goals><phase>integration-test</phase></execution></executions><configuration><executable>java</executable><classpathScope>test</classpathScope><arguments><argument>-classpath</argument><classpath /><argument>com.dev9.benchmark.BenchmarkBase</argument><argument>.*</argument></arguments></configuration></plugin></plugins></build> </profile>此代碼段顯示了一個僅運行基準測試的maven配置文件示例。
6)最后,可選項目是在您的持續集成構建管道中創建一個可運行的構建步驟。 為了獨立運行基準測試,您或您的CI可以運行:
mvn clean verify -Pbenchmark結論
如果您使用的是基于Java的項目,則相對容易地將JMH添加到您的項目和管道中。 與項目關鍵區域相關的歷史分類帳的好處對于保持較高的質量標準非常有用。 將JMH添加到您的管道還遵循持續交付原則,包括反饋循環,自動化,可重復和不斷改進。 考慮將JMH線束和一些測試添加到解決方案的關鍵區域。
翻譯自: https://www.javacodegeeks.com/2016/12/adding-microbenchmarking-build-process.html
總結
以上是生活随笔為你收集整理的在您的构建过程中添加微基准测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: keep如何设置骑行模式(keep有骑行
- 下一篇: 将速度加快到自己的个人代码生成器中