在您的构建过程中添加微基准测试
介紹
作為一個(gè)行業(yè),我們正在采用更高的透明度和更可預(yù)測(cè)的構(gòu)建過程,以減少構(gòu)建軟件的風(fēng)險(xiǎn)。 持續(xù)交付的核心原則之一是通過反饋循環(huán)收集反饋。 在Dev9中 ,我們采用了與CD原則相一致的“ 先知道 ”原則,這意味著我們(開發(fā)團(tuán)隊(duì))希望首先知道何時(shí)出現(xiàn)故障,性能下降或任何與之不符的結(jié)果。業(yè)務(wù)目標(biāo)。
Maven和其他構(gòu)建工具為開發(fā)人員提供了標(biāo)準(zhǔn)化的工具和生態(tài)系統(tǒng),可在其中建立和交流反饋。 盡管單元測(cè)試,功能,構(gòu)建驗(yàn)收,數(shù)據(jù)庫遷移,性能測(cè)試和代碼分析工具已成為開發(fā)流程中的主要內(nèi)容,但基準(zhǔn)測(cè)試基本上仍然不在流程之外。 這可能是由于缺乏開源的,低成本的工具或輕量級(jí)的庫,這些庫增加了最小的復(fù)雜性。
現(xiàn)有工具通常需要將外部工具與運(yùn)行時(shí)工件集成在一起,從而使復(fù)雜性更加復(fù)雜,并且測(cè)試未保存在同一源存儲(chǔ)庫中,甚至沒有存儲(chǔ)在源存儲(chǔ)庫中。 本地開發(fā)人員無法毫不費(fèi)力地運(yùn)行基準(zhǔn)測(cè)試,因此測(cè)試會(huì)很快失去其價(jià)值。 除了主流的解決方案問題外,基準(zhǔn)測(cè)試通常不是在課堂上講授的,并且通常在沒有必要的隔離才能獲得可靠結(jié)果的情況下實(shí)施。 這使得所有有關(guān)基準(zhǔn)測(cè)試結(jié)果的博客或帖子成為巨魔的成熟目標(biāo)。
綜上所述,圍繞代碼庫的關(guān)鍵區(qū)域進(jìn)行某種基準(zhǔn)覆蓋仍然很重要。 建立有關(guān)代碼關(guān)鍵部分的歷史知識(shí)可以幫助影響優(yōu)化工作,向團(tuán)隊(duì)通報(bào)技術(shù)欠債,在性能閾值更改已提交時(shí)發(fā)出警報(bào)并比較算法的先前版本或新版本。 現(xiàn)在的問題是,如何找到基準(zhǔn)并輕松地將基準(zhǔn)添加到我的新項(xiàng)目或現(xiàn)有項(xiàng)目中。 在此博客中,我們將專注于Java項(xiàng)目(1.7+)。 該示例代碼將利用Maven,盡管Gradle的工作原理非常相似。 我在整個(gè)博客中提出了一些建議,這些建議是基于過去項(xiàng)目的經(jīng)驗(yàn)得出的。
JHM簡介
在對(duì)基準(zhǔn)Java代碼進(jìn)行基準(zhǔn)測(cè)試時(shí),有很多強(qiáng)大的選擇,但是它們大多數(shù)都有缺點(diǎn),包括許可證費(fèi)用,額外的工具,字節(jié)代碼操縱和/或Java代理,使用非基于Java的代碼概述的測(cè)試以及高度復(fù)雜的配置設(shè)置。 我喜歡使測(cè)試盡可能接近被測(cè)代碼,以減少脆性,降低內(nèi)聚力并減少耦合。 我認(rèn)為我以前使用過的大多數(shù)基準(zhǔn)測(cè)試解決方案太麻煩了,或者運(yùn)行測(cè)試的代碼不夠孤立(完全集成在代碼中),或者包含在遠(yuǎn)離源代碼的輔助解決方案中。
本博客的目的是演示如何在構(gòu)建管道中添加輕量級(jí)基準(zhǔn)測(cè)試工具,因此我將不詳細(xì)介紹如何使用JMH,以下博客是學(xué)習(xí)的絕佳資源:
- 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/
基準(zhǔn)測(cè)試模式
關(guān)于模式和評(píng)分,我想指出一小部分,因?yàn)樗鼈冊(cè)诨九渲玫脑O(shè)置中起著重要作用。 在基本級(jí)別上,JMH有兩種主要的度量類型:吞吐量和基于時(shí)間的度量。
吞吐量測(cè)量
吞吐量是每單位時(shí)間可以完成的操作量。 隨著框架增加測(cè)試的負(fù)載量,JMH會(huì)維護(hù)成功和失敗操作的集合。 注意:確保方法或測(cè)試完全隔離,并且諸如測(cè)試對(duì)象創(chuàng)建之類的依賴項(xiàng)是在方法之外或在設(shè)置方法中進(jìn)行預(yù)測(cè)試的。 使用吞吐量時(shí),該值越高,越好,因?yàn)樗砻骺梢栽趩挝粫r(shí)間內(nèi)運(yùn)行更多的操作。
基于時(shí)間的測(cè)量
基于時(shí)間的測(cè)量是吞吐量的反伙伴。 基于時(shí)間的測(cè)量的目的是確定特定操作每單位時(shí)間運(yùn)行多長時(shí)間。
平均時(shí)間
最常見的基于時(shí)間的度量是“ AverageTime”,用于計(jì)算操作的平均時(shí)間。 JMH還將產(chǎn)生“ 得分錯(cuò)誤 ”,以幫助確定對(duì)產(chǎn)生得分的信心。 “ 得分誤差 ”通常是置信區(qū)間的1/2,表示結(jié)果與平均時(shí)間的偏離程度。 結(jié)果越低,越好,因?yàn)樗砻髅看尾僮鞯钠骄\(yùn)行時(shí)間較短。
采樣時(shí)間
SampleTime與AverageTime相似,但是JMH嘗試增加更多的負(fù)載并查找會(huì)產(chǎn)生失敗百分比矩陣的失敗。 使用AverageTime時(shí),數(shù)字越小越好,這些百分比對(duì)于確定由于吞吐量和時(shí)間長度而導(dǎo)致失敗的位置很有用。
SingleShotTime
最后也是最不常用的模式是SingleShotTime。 此模式實(shí)際上是一次運(yùn)行,可用于冷測(cè)方法或測(cè)試您的測(cè)試。 如果在運(yùn)行基準(zhǔn)測(cè)試時(shí)作為參數(shù)傳遞SingleShotTime,可能會(huì)很有用,但會(huì)減少運(yùn)行測(cè)試所需的時(shí)間(盡管這樣做會(huì)減少測(cè)試的價(jià)值并可能使它們自重)。 與其他基于時(shí)間的測(cè)量一樣,該值越低越好。
將JMH添加到Java項(xiàng)目
目標(biāo):本部分將展示如何創(chuàng)建可重復(fù)使用的工具,該工具可在不增加代碼開銷或代碼重復(fù)的情況下添加新測(cè)試。 注意,這些依賴項(xiàng)在“測(cè)試”范圍內(nèi),以避免將JMH添加到最終工件中。 我創(chuàng)建了一個(gè)在使用Protobuf替代REST for Microservices時(shí)使用JMH的github存儲(chǔ)庫。 可以在這里找到代碼: https : //github.com/mike-ensor/protobuf-serialization
1)首先將依賴項(xiàng)添加到項(xiàng)目中:
<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建議將基準(zhǔn)測(cè)試和工件包裝在同一uber jar中。 有幾種方法可以實(shí)現(xiàn)uber jar,顯式地使用made的“ shade”插件或隱式地使用Spring Boot,Dropwizard或具有類似結(jié)果的某些框架。 出于本博客文章的目的,我使用了Spring Boot應(yīng)用程序。
3)添加具有主條目類和全局配置的測(cè)試工具。 在此步驟中,在項(xiàng)目的測(cè)試區(qū)域中創(chuàng)建一個(gè)入口點(diǎn)(用#1表示)。 目的是避免將基準(zhǔn)代碼與主要工件打包在一起。
3.1)添加BenchmarkBase文件(在#2上方指示)。 此文件將用作基準(zhǔn)測(cè)試的入口,并包含測(cè)試的所有全局配置。 我編寫的類正在尋找一個(gè)包含配置屬性的“ benchmark.properties”文件(上面在#3中指出)。 JMH可以選擇輸出文件結(jié)果,并且此配置是為JSON設(shè)置的。 結(jié)果與您的持續(xù)集成工具一起使用,可以(應(yīng)該)存儲(chǔ)以供歷史使用。
此代碼段是Maven運(yùn)行的Benchmark流程的基本工具和入口點(diǎn)(在下面的步驟5中進(jìn)行設(shè)置)。此時(shí),項(xiàng)目應(yīng)該能夠運(yùn)行基準(zhǔn)測(cè)試,因此讓我們添加一個(gè)測(cè)試用例。
@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)創(chuàng)建一個(gè)類來對(duì)操作進(jìn)行基準(zhǔn)測(cè)試。 請(qǐng)記住,基準(zhǔn)測(cè)試將針對(duì)整個(gè)方法主體進(jìn)行,包括日志記錄,文件讀取,外部資源等。請(qǐng)注意要進(jìn)行基準(zhǔn)測(cè)試并減少或刪除依賴項(xiàng),以便隔離主題代碼以確保對(duì)結(jié)果的信心更高。 在此示例中,在
@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);}}標(biāo)題:該要點(diǎn)是從Protobuf序列化中提取的示例基準(zhǔn)測(cè)試用例
現(xiàn)在,當(dāng)您執(zhí)行測(cè)試jar時(shí),所有* Benchmark * .java測(cè)試類都將運(yùn)行,但這通常并不理想,因?yàn)樵撨^程沒有隔離,并且對(duì)基準(zhǔn)測(cè)試的時(shí)間和方式進(jìn)行一定的控制對(duì)于保持構(gòu)建時(shí)間很重要下。
讓我們構(gòu)建一個(gè)Maven配置文件,以控制運(yùn)行基準(zhǔn)測(cè)試的時(shí)間以及可能啟動(dòng)應(yīng)用程序的時(shí)間。 注意,為了顯示Maven集成測(cè)試啟動(dòng)/停止服務(wù)器的目的,我已將此內(nèi)容包含在博客文章中。 我會(huì)警告需要啟動(dòng)或停止應(yīng)用程序服務(wù)器,因?yàn)檫@可能會(huì)帶來資源獲取(REST調(diào)用)的成本,而這并不是很孤立。
5)概念是創(chuàng)建一個(gè)Maven配置文件以獨(dú)立運(yùn)行所有基準(zhǔn)測(cè)試(即,沒有單元測(cè)試或功能測(cè)試)。 這將使基準(zhǔn)測(cè)試可以與其余構(gòu)建管道并行運(yùn)行。 請(qǐng)注意,該代碼使用“ exec”插件并運(yùn)行uber jar,以查找指向主類的完整類路徑路徑。 此外,可執(zhí)行文件范圍僅限于“測(cè)試”源,以避免將基準(zhǔn)代碼放入最終工件中。
<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>此代碼段顯示了一個(gè)僅運(yùn)行基準(zhǔn)測(cè)試的maven配置文件示例。
6)最后,可選項(xiàng)目是在您的持續(xù)集成構(gòu)建管道中創(chuàng)建一個(gè)可運(yùn)行的構(gòu)建步驟。 為了獨(dú)立運(yùn)行基準(zhǔn)測(cè)試,您或您的CI可以運(yùn)行:
mvn clean verify -Pbenchmark結(jié)論
如果您使用的是基于Java的項(xiàng)目,則相對(duì)容易地將JMH添加到您的項(xiàng)目和管道中。 與項(xiàng)目關(guān)鍵區(qū)域相關(guān)的歷史分類帳的好處對(duì)于保持較高的質(zhì)量標(biāo)準(zhǔn)非常有用。 將JMH添加到您的管道還遵循持續(xù)交付原則,包括反饋循環(huán),自動(dòng)化,可重復(fù)和不斷改進(jìn)。 考慮將JMH線束和一些測(cè)試添加到解決方案的關(guān)鍵區(qū)域。
翻譯自: https://www.javacodegeeks.com/2016/12/adding-microbenchmarking-build-process.html
總結(jié)
以上是生活随笔為你收集整理的在您的构建过程中添加微基准测试的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: keep如何设置骑行模式(keep有骑行
- 下一篇: 将速度加快到自己的个人代码生成器中