Otimização do uso da CPU / memória de um thread em Java?
-
05-07-2019 - |
Pergunta
Eu estou escrevendo um aplicativo que terá várias threads em execução, e quer estrangular o uso da CPU / memória desses tópicos.
Há uma pergunta semelhante para C ++ , mas eu quero tentar e evitar o uso C ++ e JNI, se possível. Sei que isso pode não ser possível, utilizando uma linguagem de nível superior, mas estou curioso para ver se alguém tiver alguma idéia.
EDIT: Adicionado uma recompensa; Eu gostaria alguns realmente bom, bem pensado ideias sobre este assunto.
EDIT 2: A situação eu preciso disso para se executar o código de outras pessoas no meu servidor. Basicamente, é um código completamente arbitrária, com a única garantia é que haverá um método principal no arquivo de classe. Actualmente, várias classes completamente diferentes, que são carregados no tempo de execução, estão executando concorrentemente como segmentos separados.
A forma como está escrito, seria uma dor de refatorar para criar processos separados para cada classe que é executado. Se essa é a única boa maneira de uso de memória limite através dos argumentos VM, então que assim seja. Mas eu gostaria de saber se há uma maneira de fazê-lo com threads. Mesmo como um processo separado, eu gostaria de ser capaz de limitar de alguma forma o uso da CPU, pois, como já referi anteriormente, vários destes será a execução de uma só vez. Eu não quero um loop infinito para monopolizar todos os recursos.
EDIT 3: Uma maneira fácil de tamanho aproximado objeto está com o java aulas noreferrer Instrumentação ; especificamente, o método getObjectSize. Note-se que há alguma configuração especial necessária para usar esta ferramenta.
Solução
Se eu entender o seu problema, uma forma seria a de forma adaptativa sono dos fios, da mesma forma como a reprodução de vídeo é feito em Java. Se você sabe que quer 50% de utilização núcleo, o seu algoritmo deve dormir cerca de 0,5 segundos - potencialmente distribuídas dentro de um segundo (por exemplo 0,25 sec computação, 0,25 seg sono, e.t.c.). Aqui está um exemplo do meu player de vídeo.
long starttime = 0; // variable declared
//...
// for the first time, remember the timestamp
if (frameCount == 0) {
starttime = System.currentTimeMillis();
}
// the next timestamp we want to wake up
starttime += (1000.0 / fps);
// Wait until the desired next time arrives using nanosecond
// accuracy timer (wait(time) isn't accurate enough on most platforms)
LockSupport.parkNanos((long)(Math.max(0,
starttime - System.currentTimeMillis()) * 1000000));
Este código vai dormir com base nos quadros / segundo valor.
Para acelerador o uso de memória, você poderia envolver sua criação objeto em um método de fábrica, e usar algum tipo de semáforo com permissões limitadas como bytes para limitar o tamanho total estimado do objeto (o que você precisa para estimar o tamanho de vários objetos para ração o semáforo).
package concur;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class MemoryLimited {
private static Semaphore semaphore = new Semaphore(1024 * 1024, true);
// acquire method to get a size length array
public static byte[] createArray(int size) throws InterruptedException {
// ask the semaphore for the amount of memory
semaphore.acquire(size);
// if we get here we got the requested memory reserved
return new byte[size];
}
public static void releaseArray(byte[] array) {
// we don't need the memory of array, release
semaphore.release(array.length);
}
// allocation size, if N > 1M then there will be mutual exclusion
static final int N = 600000;
// the test program
public static void main(String[] args) {
// create 2 threaded executor for the demonstration
ExecutorService exec = Executors.newFixedThreadPool(2);
// what we want to run for allocation testion
Runnable run = new Runnable() {
@Override
public void run() {
Random rnd = new Random();
// do it 10 times to be sure we get the desired effect
for (int i = 0; i < 10; i++) {
try {
// sleep randomly to achieve thread interleaving
TimeUnit.MILLISECONDS.sleep(rnd.nextInt(100) * 10);
// ask for N bytes of memory
byte[] array = createArray(N);
// print current memory occupation log
System.out.printf("%s %d: %s (%d)%n",
Thread.currentThread().getName(),
System.currentTimeMillis(), array,
semaphore.availablePermits());
// wait some more for the next thread interleaving
TimeUnit.MILLISECONDS.sleep(rnd.nextInt(100) * 10);
// release memory, no longer needed
releaseArray(array);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// run first task
exec.submit(run);
// run second task
exec.submit(run);
// let the executor exit when it has finished processing the runnables
exec.shutdown();
}
}
Outras dicas
Você pode obter um monte de informações sobre o uso de CPU e memória através de JMX , mas eu não acho que ele permite que qualquer manipulação ativa.
Para controlar o uso da CPU até certo ponto, você pode usar Thread.setPriority () .
Quanto à memória, não existe tal coisa como memória por thread. O próprio conceito de Java Threads meio de memória compartilhada. A única maneira de uso de memória de controle é através das opções de linha de comando como -Xmx, mas não há nenhuma maneira de manipular as configurações em tempo de execução.
Care of Java Fóruns . Basicamente o seu timing de execução e, em seguida, à espera quando seu levando muito tempo. Como é mencionado no segmento original, executar este em um segmento separado e interrompendo o fio trabalho dará resultados mais precisos, como valores vontade de média ao longo do tempo.
import java.lang.management.*;
ThreadMXBean TMB = ManagementFactory.getThreadMXBean();
long time = new Date().getTime() * 1000000;
long cput = 0;
double cpuperc = -1;
while(true){
if( TMB.isThreadCpuTimeSupported() ){
if(new Date().getTime() * 1000000 - time > 1000000000){ //Reset once per second
time = new Date().getTime() * 1000000;
cput = TMB.getCurrentThreadCpuTime();
}
if(!TMB.isThreadCpuTimeEnabled()){
TMB.setThreadCpuTimeEnabled(true);
}
if(new Date().getTime() * 1000000 - time != 0)
cpuperc = (TMB.getCurrentThreadCpuTime() - cput) / (new Date().getTime() * 1000000.0 - time) * 100.0;
}
//If cpu usage is greater then 50%
if(cpuperc > 50.0){
//sleep for a little bit.
continue;
}
//Do cpu intensive stuff
}
Você pode atribuir diferentes prioridades para os tópicos de modo que o segmento mais relevante se agendado com mais freqüência.
Look at this resposta para ver se isso ajuda.
Quando todo o segmento em execução têm a mesma prioridade que pode ser executado como este:
t1, t2, t3, t1, t2, t3, t1, t2, t3
Quando você atribuir uma prioridade diferente para um deles pode ser parecido:
t1, t1, t1, t1, t2, t1, t1, t1 t3.
Isto é, o primeiro segmento é executado "mais frequentemente" que o resto.
Se você executar os fios em um processo separado pode limitar o uso de memória e limitam o número de CPUs ou alterar a prioridade destes tópicos.
No entanto, qualquer coisa que você faz é provável adicionar sobrecarga ea complexidade que muitas vezes é contraproducente.
A menos que você pode explicar por que você iria querer fazer isso (por exemplo, você tem uma biblioteca de mal escrito que você não confia e não pode obter suporte para) eu sugiro que você não precisa.
A razão não é fácil para restringir o uso de memória é que há apenas um amontoado que é compartilhada. Portanto, um objeto que é usado em uma thread é utilizável em outro e não é atribuído a um segmento ou de outra.
A limitação de CPU meios de uso parando todos os fios para que eles não fazem nada, no entanto, uma abordagem melhor é certificar-se o fio não perca CPU e só estão activas fazendo o trabalho que precisa ser feito, no caso de você que não gostaria de detê-los a fazê-lo.
Por que não em vez de fazer "enfiar" fazer multitarefa cooperativa, seria interessante ver se você pode manipular http: / /www.janino.net/ para executar um programa para um determinado período de tempo / conjunto de intstructions, em seguida, parar e executar o próximo programa. Pelo menos assim o seu justo, dar a todos a mesma fatia de tempo ...
Thread.setPriority () pode ajudar, mas não permitem que você limitar o CPU usado por um fio. Na verdade, eu não ouvi falar de qualquer biblioteca Java que faz isso.
Pode ser possível implementar tal facilidade a condição de que seus tópicos estão dispostos a cooperar. A chave é ter os fios periodicamente põe em um agendador personalizado, e têm o uso do monitor Agendador de thread CPU usando JMX. Mas o problema é que, se alguma linha não fazer a chamada programador muitas vezes ele pode muito bem exceder os limites de estrangulamento. E não há nada que você possa fazer em relação a um segmento que fica preso em um loop.
Outra rota teórica para a implementação seria usar isolados. Infelizmente, você vai ter dificuldade para encontrar uma JVM de uso geral que implementa isolados. Além disso, as APIs padrão só permitem-lhe controlar o isolado, e não os tópicos dentro do isolado.
A única maneira você pode limitar o uso de CPU da linha é por qualquer bloco em um recurso ou rendimento call () freqüentemente.
Isso não limita o uso da CPU abaixo de 100%, mas dá outros segmentos e processos mais timeslices.
Para reduzir CPU, você quer dormir seus tópicos dentro de comum se e enquanto loops.
while(whatever) {
//do something
//Note the capitol 'T' here, this sleeps the current thread.
Thread.sleep(someNumberOfMilliSeconds);
}
Sleeping por algumas centenas de milissegundos irá reduzir significativamente o uso da CPU com pouco ou nenhum resultado notável no desempenho.
Quanto à memória, eu executar um profiler sobre os tópicos individuais e fazer algum ajuste de desempenho. Se você fora estrangulado a quantidade de memória disponível para enfiar Eu acho que uma exceção de memória ou fio de fome é provável. Eu confiaria a JVM para fornecer o máximo de memória como o fio necessário e trabalhar na redução do uso de memória, mantendo apenas os objetos essenciais no âmbito a qualquer momento.