sjavac(smarter java compilation)最先在openjdk8中提供了初級版本,其初衷是用來加速jdk本身的編譯。在9中進行過一版優化,使其更加穩定可靠,可以用來編譯任意的大型java項目。
其其實是一個包裝在原始javac外部的wrapper實現。
sjavac在javac的基礎上實現了:
多線程編譯(javac是單線程的),利用多核並行加速編譯.
提供一個tcp server模式,暴露編譯服務. 利用jit編譯加速編譯.
基於時間戳+pubapi比較的方式來實施增量編譯
在java編譯領域,gradle和bazel也採起了相似的思路來加速編譯,實現增量(bazel和gradle的增量檢測更嚴謹,其採起文件內容摘要而非時間戳來判斷文件是否修改),且已投入工業界被大量使用。總的來講,sjavac並不算是很是新穎或先進的技術,開發能直接使用sjavac的場景也不多,ide方面卻是能夠集成sjavac來優化其編譯速度。
從實際測試效果來看,sjavac的編譯速度因爲多核並行確實會有較大提高。接下來,咱們看看sjavac中比較有趣的地方是如何實現的。
1.首先咱們看看其是如何實現並行編譯的
a. 在sjavac8中,多線程編譯實現比較簡單。客戶端將待編譯的文件按包分組,同一個包老是由同一個javac實例編譯,多個不一樣的包能夠由同一個javac實例編譯,每一個組分別發送到服務端請求編譯。並使用-implicit:none參數禁止輸出隱式編譯產物,從而在每一個核心上進行徹底獨立的編譯。這種並行方式,源文件之間若存在依賴的話,並行度不會受到影響,但同一個源文件可能會進行屢次重複編譯,cpu使用率較低,且總耗時的收益會受到影響。極端狀況下,n個鏈式依賴的源文件,編譯時間不比單線程更快,卻可能有n-1個編譯任務都是徹底冗餘的。
for (String pkgName : packageNames) {
CompileChunk cc = compileChunks[ci];
Set<URI> s = pkgSrcs.get(pkgName);
if (cc.srcs.size()+s.size() > sourcesPerCompile && ci < numCompiles-1) {
from = null;
ci++;
cc = compileChunks[ci];
}
cc.numPackages++;
cc.srcs.addAll(s);
// Calculate nice package names to use as information when compiling.
String justPkgName = Util.justPackageName(pkgName);
// Fetch how many packages depend on this package from the old build state.
Set<String> ss = oldPackageDependents.get(pkgName);
if (ss != null) {
// Accumulate this information onto this chunk.
cc.numDependents += ss.size();
}
if (from == null || from.trim().equals("")) from = justPkgName;
cc.pkgNames.append(justPkgName+"("+s.size()+") ");
cc.pkgFromTos = from+" to "+justPkgName;
}
根據拆分出來的任務,使用不一樣的線程進行構建。每次sjavac.compile調用都會建立新的com.sun.tools.javac.main.Main實例
for (int i=0; i<numCompiles; ++i) {
final int ii = i;
final CompileChunk cc = compileChunks[i];
requests[i] = new Thread() {
@Override
public void run() {
rn[ii] = sjavac.compile("n/a",
id + "-" + ii,
args.prepJavacArgs(),
Collections.<File>emptyList(),
cc.srcs,
visibleSources);
packageArtifacts.putAll(rn[ii].packageArtifacts);
packageDependencies.putAll(rn[ii].packageDependencies);
packagePublicApis.putAll(rn[ii].packagePublicApis);
classpathPackageDependencies.putAll(rn[ii].classpathPackageDependencies);
}
};
b.在sjavac9中,爲了解決重複對同一個源文件反覆隱式編譯的問題,對並行編譯任務之間的數據共享作了優化。
2.增量編譯
不一樣於maven只編譯變化的文件和按照模塊爲單位進行增量編譯。sjavac實現了按照文件粒度的增量編譯。其將每一個文件和依賴的public api即公有方法,公有屬性轉化成一個hashcode,用來比較文件是否修改了pubapi。若是沒有修改pubapi,則編譯不須要傳遞,能夠僅編譯變化的部分。不然按照依賴關係進行傳遞,最終獲得須要從新編譯的文件集合。
整體來看,sjavac並非一個實用的工具,可是其中的一些思想可能已經被gradle,bazel等編譯工具借鑑。也是值得借鑑的。