1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/share/classes/com/sun/tools/sjavac/CompileJavaPackages.java Mon Feb 04 18:08:53 2013 -0500 1.3 @@ -0,0 +1,344 @@ 1.4 +/* 1.5 + * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. 1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.7 + * 1.8 + * This code is free software; you can redistribute it and/or modify it 1.9 + * under the terms of the GNU General Public License version 2 only, as 1.10 + * published by the Free Software Foundation. Oracle designates this 1.11 + * particular file as subject to the "Classpath" exception as provided 1.12 + * by Oracle in the LICENSE file that accompanied this code. 1.13 + * 1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT 1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1.17 + * version 2 for more details (a copy is included in the LICENSE file that 1.18 + * accompanied this code). 1.19 + * 1.20 + * You should have received a copy of the GNU General Public License version 1.21 + * 2 along with this work; if not, write to the Free Software Foundation, 1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1.23 + * 1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1.25 + * or visit www.oracle.com if you need additional information or have any 1.26 + * questions. 1.27 + */ 1.28 + 1.29 +package com.sun.tools.sjavac; 1.30 + 1.31 +import java.net.URI; 1.32 +import java.util.Arrays; 1.33 +import java.util.Random; 1.34 +import java.util.Set; 1.35 +import java.util.Map; 1.36 + 1.37 +import com.sun.tools.sjavac.server.JavacServer; 1.38 +import com.sun.tools.sjavac.server.SysInfo; 1.39 +import java.io.PrintStream; 1.40 + 1.41 +/** 1.42 + * This transform compiles a set of packages containing Java sources. 1.43 + * The compile request is divided into separate sets of source files. 1.44 + * For each set a separate request thread is dispatched to a javac server 1.45 + * and the meta data is accumulated. The number of sets correspond more or 1.46 + * less to the number of cores. Less so now, than it will in the future. 1.47 + * 1.48 + * <p><b>This is NOT part of any supported API. 1.49 + * If you write code that depends on this, you do so at your own 1.50 + * risk. This code and its internal interfaces are subject to change 1.51 + * or deletion without notice.</b></p> 1.52 + */ 1.53 +public class CompileJavaPackages implements Transformer { 1.54 + 1.55 + // The current limited sharing of data between concurrent JavaCompilers 1.56 + // in the server will not give speedups above 3 cores. Thus this limit. 1.57 + // We hope to improve this in the future. 1.58 + final static int limitOnConcurrency = 3; 1.59 + 1.60 + String serverSettings; 1.61 + public void setExtra(String e) { 1.62 + serverSettings = e; 1.63 + } 1.64 + 1.65 + String[] args; 1.66 + public void setExtra(String[] a) { 1.67 + args = a; 1.68 + } 1.69 + 1.70 + public boolean transform(Map<String,Set<URI>> pkgSrcs, 1.71 + Set<URI> visibleSources, 1.72 + Map<URI,Set<String>> visibleClasses, 1.73 + Map<String,Set<String>> oldPackageDependents, 1.74 + URI destRoot, 1.75 + final Map<String,Set<URI>> packageArtifacts, 1.76 + final Map<String,Set<String>> packageDependencies, 1.77 + final Map<String,String> packagePubapis, 1.78 + int debugLevel, 1.79 + boolean incremental, 1.80 + int numCores, 1.81 + PrintStream out, 1.82 + PrintStream err) 1.83 + { 1.84 + boolean rc = true; 1.85 + boolean concurrentCompiles = true; 1.86 + 1.87 + // Fetch the id. 1.88 + String id = Util.extractStringOption("id", serverSettings); 1.89 + if (id == null || id.equals("")) { 1.90 + // No explicit id set. Create a random id so that the requests can be 1.91 + // grouped properly in the server. 1.92 + id = "id"+(((new Random()).nextLong())&Long.MAX_VALUE); 1.93 + } 1.94 + // Only keep portfile and sjavac settings.. 1.95 + String psServerSettings = Util.cleanSubOptions("--server:", Util.set("portfile","sjavac","background","keepalive"), serverSettings); 1.96 + 1.97 + // Get maximum heap size from the server! 1.98 + SysInfo sysinfo = JavacServer.connectGetSysInfo(psServerSettings, out, err); 1.99 + if (sysinfo.numCores == -1) { 1.100 + Log.error("Could not query server for sysinfo!"); 1.101 + return false; 1.102 + } 1.103 + int numMBytes = (int)(sysinfo.maxMemory / ((long)(1024*1024))); 1.104 + Log.debug("Server reports "+numMBytes+"MiB of memory and "+sysinfo.numCores+" cores"); 1.105 + 1.106 + if (numCores <= 0) { 1.107 + // Set the requested number of cores to the number of cores on the server. 1.108 + numCores = sysinfo.numCores; 1.109 + Log.debug("Number of jobs not explicitly set, defaulting to "+sysinfo.numCores); 1.110 + } else if (sysinfo.numCores < numCores) { 1.111 + // Set the requested number of cores to the number of cores on the server. 1.112 + Log.debug("Limiting jobs from explicitly set "+numCores+" to cores available on server: "+sysinfo.numCores); 1.113 + numCores = sysinfo.numCores; 1.114 + } else { 1.115 + Log.debug("Number of jobs explicitly set to "+numCores); 1.116 + } 1.117 + // More than three concurrent cores does not currently give a speedup, at least for compiling the jdk 1.118 + // in the OpenJDK. This will change in the future. 1.119 + int numCompiles = numCores; 1.120 + if (numCores > limitOnConcurrency) numCompiles = limitOnConcurrency; 1.121 + // Split the work up in chunks to compiled. 1.122 + 1.123 + int numSources = 0; 1.124 + for (String s : pkgSrcs.keySet()) { 1.125 + Set<URI> ss = pkgSrcs.get(s); 1.126 + numSources += ss.size(); 1.127 + } 1.128 + 1.129 + int sourcesPerCompile = numSources / numCompiles; 1.130 + 1.131 + // For 64 bit Java, it seems we can compile the OpenJDK 8800 files with a 1500M of heap 1.132 + // in a single chunk, with reasonable performance. 1.133 + // For 32 bit java, it seems we need 1G of heap. 1.134 + // Number experimentally determined when compiling the OpenJDK. 1.135 + // Includes space for reasonably efficient garbage collection etc, 1.136 + // Calculating backwards gives us a requirement of 1.137 + // 1500M/8800 = 175 KiB for 64 bit platforms 1.138 + // and 1G/8800 = 119 KiB for 32 bit platform 1.139 + // for each compile..... 1.140 + int kbPerFile = 175; 1.141 + String osarch = System.getProperty("os.arch"); 1.142 + if (osarch.equals("i386")) { 1.143 + // For 32 bit platforms, assume it is slightly smaller 1.144 + // because of smaller object headers and pointers. 1.145 + kbPerFile = 119; 1.146 + } 1.147 + int numRequiredMBytes = (kbPerFile*numSources)/1024; 1.148 + Log.debug("For os.arch "+osarch+" the empirically determined heap required per file is "+kbPerFile+"KiB"); 1.149 + Log.debug("Server has "+numMBytes+"MiB of heap."); 1.150 + Log.debug("Heuristics say that we need "+numRequiredMBytes+"MiB of heap for all source files."); 1.151 + // Perform heuristics to see how many cores we can use, 1.152 + // or if we have to the work serially in smaller chunks. 1.153 + if (numMBytes < numRequiredMBytes) { 1.154 + // Ouch, cannot fit even a single compile into the heap. 1.155 + // Split it up into several serial chunks. 1.156 + concurrentCompiles = false; 1.157 + // Limit the number of sources for each compile to 500. 1.158 + if (numSources < 500) { 1.159 + numCompiles = 1; 1.160 + sourcesPerCompile = numSources; 1.161 + Log.debug("Compiling as a single source code chunk to stay within heap size limitations!"); 1.162 + } else if (sourcesPerCompile > 500) { 1.163 + // This number is very low, and tuned to dealing with the OpenJDK 1.164 + // where the source is >very< circular! In normal application, 1.165 + // with less circularity the number could perhaps be increased. 1.166 + numCompiles = numSources / 500; 1.167 + sourcesPerCompile = numSources/numCompiles; 1.168 + Log.debug("Compiling source as "+numCompiles+" code chunks serially to stay within heap size limitations!"); 1.169 + } 1.170 + } else { 1.171 + if (numCompiles > 1) { 1.172 + // Ok, we can fit at least one full compilation on the heap. 1.173 + float usagePerCompile = (float)numRequiredMBytes / ((float)numCompiles * (float)0.7); 1.174 + int usage = (int)(usagePerCompile * (float)numCompiles); 1.175 + Log.debug("Heuristics say that for "+numCompiles+" concurrent compiles we need "+usage+"MiB"); 1.176 + if (usage > numMBytes) { 1.177 + // Ouch it does not fit. Reduce to a single chunk. 1.178 + numCompiles = 1; 1.179 + sourcesPerCompile = numSources; 1.180 + // What if the relationship betweem number of compile_chunks and num_required_mbytes 1.181 + // is not linear? Then perhaps 2 chunks would fit where 3 does not. Well, this is 1.182 + // something to experiment upon in the future. 1.183 + Log.debug("Limiting compile to a single thread to stay within heap size limitations!"); 1.184 + } 1.185 + } 1.186 + } 1.187 + 1.188 + Log.debug("Compiling sources in "+numCompiles+" chunk(s)"); 1.189 + 1.190 + // Create the chunks to be compiled. 1.191 + final CompileChunk[] compileChunks = createCompileChunks(pkgSrcs, oldPackageDependents, 1.192 + numCompiles, sourcesPerCompile); 1.193 + 1.194 + if (Log.isDebugging()) { 1.195 + int cn = 1; 1.196 + for (CompileChunk cc : compileChunks) { 1.197 + Log.debug("Chunk "+cn+" for "+id+" ---------------"); 1.198 + cn++; 1.199 + for (URI u : cc.srcs) { 1.200 + Log.debug(""+u); 1.201 + } 1.202 + } 1.203 + } 1.204 + 1.205 + // The return values for each chunked compile. 1.206 + final int[] rn = new int[numCompiles]; 1.207 + // The requets, might or might not run as a background thread. 1.208 + final Thread[] requests = new Thread[numCompiles]; 1.209 + 1.210 + final Set<URI> fvisible_sources = visibleSources; 1.211 + final Map<URI,Set<String>> fvisible_classes = visibleClasses; 1.212 + 1.213 + long start = System.currentTimeMillis(); 1.214 + 1.215 + for (int i=0; i<numCompiles; ++i) { 1.216 + final int ii = i; 1.217 + final CompileChunk cc = compileChunks[i]; 1.218 + 1.219 + // Pass the num_cores and the id (appended with the chunk number) to the server. 1.220 + final String cleanedServerSettings = psServerSettings+",poolsize="+numCores+",id="+id+"-"+ii; 1.221 + final PrintStream fout = out; 1.222 + final PrintStream ferr = err; 1.223 + 1.224 + requests[ii] = new Thread() { 1.225 + @Override 1.226 + public void run() { 1.227 + rn[ii] = JavacServer.useServer(cleanedServerSettings, 1.228 + Main.removeWrapperArgs(args), 1.229 + cc.srcs, 1.230 + fvisible_sources, 1.231 + fvisible_classes, 1.232 + packageArtifacts, 1.233 + packageDependencies, 1.234 + packagePubapis, 1.235 + null, 1.236 + fout, ferr); 1.237 + } 1.238 + }; 1.239 + 1.240 + if (cc.srcs.size() > 0) { 1.241 + String numdeps = ""; 1.242 + if (cc.numDependents > 0) numdeps = "(with "+cc.numDependents+" dependents) "; 1.243 + if (!incremental || cc.numPackages > 16) { 1.244 + String info = "("+cc.pkgFromTos+")"; 1.245 + if (info.equals("( to )")) { 1.246 + info = ""; 1.247 + } 1.248 + Log.info("Compiling "+cc.srcs.size()+" files "+numdeps+"in "+cc.numPackages+" packages "+info); 1.249 + } else { 1.250 + Log.info("Compiling "+cc.pkgNames+numdeps); 1.251 + } 1.252 + if (concurrentCompiles) { 1.253 + requests[ii].start(); 1.254 + } 1.255 + else { 1.256 + requests[ii].run(); 1.257 + // If there was an error, then stop early when running single threaded. 1.258 + if (rn[i] != 0) { 1.259 + return false; 1.260 + } 1.261 + } 1.262 + } 1.263 + } 1.264 + if (concurrentCompiles) { 1.265 + // If there are background threads for the concurrent compiles, then join them. 1.266 + for (int i=0; i<numCompiles; ++i) { 1.267 + try { requests[i].join(); } catch (InterruptedException e) { } 1.268 + } 1.269 + } 1.270 + 1.271 + // Check the return values. 1.272 + for (int i=0; i<numCompiles; ++i) { 1.273 + if (compileChunks[i].srcs.size() > 0) { 1.274 + if (rn[i] != 0) { 1.275 + rc = false; 1.276 + } 1.277 + } 1.278 + } 1.279 + long duration = System.currentTimeMillis() - start; 1.280 + long minutes = duration/60000; 1.281 + long seconds = (duration-minutes*60000)/1000; 1.282 + Log.debug("Compilation of "+numSources+" source files took "+minutes+"m "+seconds+"s"); 1.283 + 1.284 + return rc; 1.285 + } 1.286 + 1.287 + 1.288 + /** 1.289 + * Split up the sources into compile chunks. If old package dependents information 1.290 + * is available, sort the order of the chunks into the most dependent first! 1.291 + * (Typically that chunk contains the java.lang package.) In the future 1.292 + * we could perhaps improve the heuristics to put the sources into even more sensible chunks. 1.293 + * Now the package are simple sorted in alphabetical order and chunked, then the chunks 1.294 + * are sorted on how dependent they are. 1.295 + * 1.296 + * @param pkgSrcs The sources to compile. 1.297 + * @param oldPackageDependents Old package dependents, if non-empty, used to sort the chunks. 1.298 + * @param numCompiles The number of chunks. 1.299 + * @param sourcesPerCompile The number of sources per chunk. 1.300 + * @return 1.301 + */ 1.302 + CompileChunk[] createCompileChunks(Map<String,Set<URI>> pkgSrcs, 1.303 + Map<String,Set<String>> oldPackageDependents, 1.304 + int numCompiles, 1.305 + int sourcesPerCompile) { 1.306 + 1.307 + CompileChunk[] compileChunks = new CompileChunk[numCompiles]; 1.308 + for (int i=0; i<compileChunks.length; ++i) { 1.309 + compileChunks[i] = new CompileChunk(); 1.310 + } 1.311 + 1.312 + // Now go through the packages and spread out the source on the different chunks. 1.313 + int ci = 0; 1.314 + // Sort the packages 1.315 + String[] packageNames = pkgSrcs.keySet().toArray(new String[0]); 1.316 + Arrays.sort(packageNames); 1.317 + String from = null; 1.318 + for (String pkgName : packageNames) { 1.319 + CompileChunk cc = compileChunks[ci]; 1.320 + Set<URI> s = pkgSrcs.get(pkgName); 1.321 + if (cc.srcs.size()+s.size() > sourcesPerCompile && ci < numCompiles-1) { 1.322 + from = null; 1.323 + ci++; 1.324 + cc = compileChunks[ci]; 1.325 + } 1.326 + cc.numPackages++; 1.327 + cc.srcs.addAll(s); 1.328 + 1.329 + // Calculate nice package names to use as information when compiling. 1.330 + String justPkgName = Util.justPackageName(pkgName); 1.331 + // Fetch how many packages depend on this package from the old build state. 1.332 + Set<String> ss = oldPackageDependents.get(pkgName); 1.333 + if (ss != null) { 1.334 + // Accumulate this information onto this chunk. 1.335 + cc.numDependents += ss.size(); 1.336 + } 1.337 + if (from == null || from.trim().equals("")) from = justPkgName; 1.338 + cc.pkgNames.append(justPkgName+"("+s.size()+") "); 1.339 + cc.pkgFromTos = from+" to "+justPkgName; 1.340 + } 1.341 + // If we are compiling serially, sort the chunks, so that the chunk (with the most dependents) (usually the chunk 1.342 + // containing java.lang.Object, is to be compiled first! 1.343 + // For concurrent compilation, this does not matter. 1.344 + Arrays.sort(compileChunks); 1.345 + return compileChunks; 1.346 + } 1.347 +}