Регулирование использования ЦП / памяти для потока в Java? - PullRequest
42 голосов
/ 29 июля 2009

Я пишу приложение, в котором будут запущены несколько потоков, и хочу ограничить использование ЦП / памяти этими потоками.

Существует аналогичный вопрос для C ++ , но я хочу попытаться по возможности избегать использования C ++ и JNI. Я понимаю, что это может быть невозможно при использовании языка более высокого уровня, но мне любопытно посмотреть, есть ли у кого-нибудь какие-либо идеи.

РЕДАКТИРОВАТЬ: Добавлена ​​награда; Я хотел бы получить действительно хорошие, хорошо продуманные идеи на этот счет.

РЕДАКТИРОВАТЬ 2: Ситуация, в которой я нуждаюсь, это выполнение кода других людей на моем сервере. По сути, это совершенно произвольный код, с единственной гарантией, что в файле класса будет основной метод. В настоящее время несколько совершенно разных классов, которые загружаются во время выполнения, одновременно выполняются как отдельные потоки.

Как написано, рефакторинг было бы затруднительно создавать отдельные процессы для каждого исполняемого класса. Если это единственный хороший способ ограничить использование памяти с помощью аргументов виртуальной машины, пусть будет так. Но я хотел бы знать, есть ли способ сделать это с потоками. Даже в качестве отдельного процесса я хотел бы иметь возможность каким-то образом ограничить использование его ЦП, поскольку, как я упоминал ранее, некоторые из них будут выполняться одновременно. Я не хочу, чтобы бесконечный цикл занимал все ресурсы.

РЕДАКТИРОВАТЬ 3: Простой способ приблизить размер объекта с помощью Инструментов классов Java; в частности, метод getObjectSize. Обратите внимание, что для использования этого инструмента необходимы специальные настройки.

Ответы [ 9 ]

32 голосов
/ 30 июля 2009

Если я понимаю вашу проблему, одним из способов будет адаптивный спящий поток, аналогично воспроизведению видео в Java. Если вы знаете, что хотите использовать ядро ​​на 50%, ваш алгоритм должен находиться в спящем режиме примерно 0,5 секунды - возможно, он будет распределен в течение одной секунды (например, вычисление 0,25 секунды, ожидание 0,25 секунды и т. Д.). Вот пример с моего видеоплеера.

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));

Этот код будет спать в зависимости от значения кадров / секунда.

Чтобы ограничить использование памяти, вы можете заключить создание вашего объекта в фабричный метод и использовать какой-то семафор с ограниченными разрешениями в виде байтов для ограничения общего предполагаемого размера объекта (вам необходимо оценить размер различных объектов, чтобы рацион семафора).

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();
    }
}
5 голосов
/ 06 августа 2009

Забота о форумах Java . В основном, время выполнения и ожидание, когда у вас слишком много времени. Как упоминалось в исходном потоке, выполнение этого в отдельном потоке и прерывание рабочего потока даст более точные результаты, а также усреднение значений по времени.

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
}
5 голосов
/ 29 июля 2009

Вы можете получить много информации об использовании процессора и памяти через JMX , но я не думаю, что это позволяет какие-либо активные манипуляции.

Для некоторого контроля использования процессора вы можете использовать Thread.setPriority () .

Что касается памяти, не существует такой вещи, как память на поток. Само понятие потоков Java означает разделяемую память. Единственный способ контролировать использование памяти - использовать параметры командной строки, такие как -Xmx, но нет способа манипулировать настройками во время выполнения.

1 голос
/ 07 августа 2009

Почему бы вместо выполнения "многопоточности" не выполнять совместную многозадачность, было бы интересно посмотреть, можете ли вы манипулировать http://www.janino.net/, чтобы запустить программу в течение определенного количества времени / набора операций, затем остановите и запустите следующая программа По крайней мере, так справедливо, дайте всем одинаковый кусочек времени ...

1 голос
/ 06 августа 2009

Если вы запускаете потоки в отдельном процессе, вы можете ограничить использование памяти и ограничить количество процессоров или изменить приоритет этих потоков.

Однако все, что вы делаете, может привести к дополнительным расходам и сложности, что часто приводит к обратным результатам.

Если вы не можете объяснить, почему вы хотите это сделать (например, у вас плохо написанная библиотека, которой вы не доверяете и не можете получить поддержку), я бы посоветовал вам этого не делать.

Причина, по которой не так просто ограничить использование памяти, заключается в том, что используется только одна куча. Таким образом, объект, который используется в одном потоке, может использоваться в другом и не назначается ни одному, ни другому.

Ограничение использования ЦП означает остановку всех потоков, чтобы они ничего не делали, однако лучший подход состоит в том, чтобы убедиться, что поток не тратит ресурсы ЦП и только активно выполняет работу, которую необходимо выполнить, и в этом случае не хотел бы помешать им сделать это.

1 голос
/ 29 июля 2009

Вы можете назначать потокам разные приоритеты, чтобы наиболее релевантные потоки планировались чаще.

Посмотрите на этот ответ и посмотрите, поможет ли это.

Когда все запущенные потоки имеют одинаковый приоритет, они могут работать так:

t1, t2, t3,     t1, t2, t3,   t1, t2, t3

Когда вы назначаете другой приоритет одному из них, он может выглядеть следующим образом:

t1, t1, t1, t1,    t2,    t1, t1, t1 t3.

То есть первый поток запускается "чаще", чем остальные.

0 голосов
/ 05 августа 2009

Чтобы уменьшить нагрузку на процессор, вы хотите усыпить свои потоки внутри общих , если и , тогда как повторяется.

while(whatever) {
    //do something
    //Note the capitol 'T' here, this sleeps the current thread.
    Thread.sleep(someNumberOfMilliSeconds);
}

Сон в течение нескольких сотен миллисекунд значительно сократит загрузку ЦП, практически не сказываясь на производительности.

Что касается памяти, я запускаю профилировщик для отдельных потоков и выполняю некоторую настройку производительности. Если вы исчерпали объем памяти, доступный потоку, я думаю, что исключение нехватки памяти или истощение потока вероятно. Я бы доверял JVM предоставить столько памяти, сколько необходимо потоку, и работать над сокращением использования памяти, сохраняя в данный момент только важные объекты в области видимости.

0 голосов
/ 30 июля 2009

Единственный способ ограничить использование ЦП потока - это либо блокировать ресурс, либо часто вызывать yield ().

Это не ограничивает использование ЦП ниже 100%, но дает другим потокам и обрабатывает больше временных интервалов.

0 голосов
/ 30 июля 2009

Thread.setPriority () может помочь, но не позволяет ограничить процессор, используемый потоком. На самом деле, я не слышал ни о какой библиотеке Java, которая делает это.

Может быть возможно реализовать такую ​​возможность, если ваши потоки готовы к сотрудничеству. Ключ заключается в том, чтобы потоки периодически вызывали пользовательский планировщик, и чтобы планировщик отслеживал загрузку ЦП с помощью JMX. Но проблема в том, что если какой-то поток не выполняет вызов планировщика достаточно часто, он может значительно превысить пределы регулирования. И вы ничего не можете сделать с потоком, который застревает в цикле.

Еще один теоретический путь к реализации - использование изолятов. К сожалению, вам будет сложно найти универсальную JVM, которая реализует изоляты. Кроме того, стандартные API-интерфейсы позволяют контролировать только изолят, а не потоки внутри изолята.

...