여러 스레드가 사용 가능할 때 여러 CPU를 사용하도록 강제
나는 그것이하는 일의 특성 때문에 많은 CPU를 사용하는 Java 프로그램을 작성하고 있습니다. 그러나 많은 것이 병렬로 실행될 수 있으며 내 프로그램을 다중 스레드로 만들었습니다. 내가 그것을 실행할 때, 그것은 더 많은 것을 필요로 할 때까지 하나의 CPU를 사용하는 것처럼 보입니다. 다른 CPU를 사용합니다. Java에서 다른 스레드가 다른 코어 / CPU에서 실행되도록 강제 할 수있는 작업이 있습니까?
내가 그것을 실행할 때, 그것은 더 많은 것을 필요로 할 때까지 하나의 CPU를 사용하는 것처럼 보입니다. 다른 CPU를 사용합니다. Java에서 다른 스레드가 다른 코어 / CPU에서 실행되도록 강제 할 수있는 작업이 있습니까?
귀하의 질문 에서이 부분은 귀하의 응용 프로그램을 다중 스레드가 가능하게 만드는 문제를 이미 해결했음을 의미합니다. 그럼에도 불구하고 다중 코어를 즉시 사용하기 시작하지는 않습니다.
"강제 할 수있는 방법이 있습니까?"에 대한 대답은 (AFAIK) 직접적이지 않습니다. JVM 및 / 또는 호스트 OS는 사용할 '기본'스레드 수와 해당 스레드가 물리적 프로세서에 매핑되는 방식을 결정합니다. 튜닝을위한 몇 가지 옵션이 있습니다. 예를 들어 Solaris에서 Java 스레딩을 조정하는 방법에 대해 설명하는 이 페이지 를 찾았습니다 . 그리고 이 페이지 멀티 스레드 응용 프로그램을 늦출 수있는 다른 것들에 대해 이야기합니다.
Java에서 다중 스레드를 수행하는 두 가지 기본 방법이 있습니다. 이러한 방법으로 생성하는 각 논리적 작업은 필요하고 사용 가능할 때 새로운 코어에서 실행되어야합니다.
방법 1 : Runnable 또는 Thread 객체 (생성자에서 Runnable을 사용할 수 있음)를 정의하고 Thread.start () 메서드로 실행을 시작합니다. OS가 제공하는 모든 코어에서 실행됩니다. 일반적으로로드가 적은 코어에서 실행됩니다.
자습서 : 스레드 정의 및 시작
방법 2 : 처리 코드를 포함하는 Runnable (값을 반환하지 않는 경우) 또는 Callable (있는 경우) 인터페이스를 구현하는 개체를 정의합니다. java.util.concurrent 패키지에서 ExecutorService에 태스크로 전달하십시오. java.util.concurrent.Executors 클래스에는 표준적이고 유용한 ExecutorServices를 생성하는 여러 메소드가 있습니다. Executors 튜토리얼 링크 .
개인적인 경험으로 볼 때 Executors 고정 및 캐시 스레드 풀은 매우 훌륭하지만 스레드 수를 조정하고 싶을 것입니다. Runtime.getRuntime (). availableProcessors ()는 런타임에 사용 가능한 코어 수를 계산하는 데 사용할 수 있습니다. 애플리케이션이 완료되면 스레드 풀을 종료해야합니다. 그렇지 않으면 ThreadPool 스레드가 계속 실행되기 때문에 애플리케이션이 종료되지 않습니다.
좋은 멀티 코어 성능을 얻는 것은 때때로 까다 롭고 문제로 가득 차 있습니다.
- 디스크 I / O는 병렬로 실행될 때 많이 느려집니다. 한 번에 하나의 스레드 만 디스크 읽기 / 쓰기를 수행해야합니다.
- 개체 동기화는 다중 스레드 작업에 안전을 제공하지만 작업 속도는 느려집니다.
- 작업이 너무 사소한 경우 (작은 작업 비트, 빠르게 실행) ExecutorService에서 작업을 관리하는 오버 헤드는 다중 코어에서 얻는 것보다 더 많은 비용이 듭니다.
- 새로운 Thread 객체를 만드는 것은 느립니다. ExecutorServices는 가능한 경우 기존 스레드를 재사용하려고합니다.
- 여러 스레드가 작업을 수행 할 때 모든 종류의 미친 일이 발생할 수 있습니다. 시스템을 단순하게 유지하고 작업을 논리적으로 구별되고 상호 작용하지 않도록 만드십시오.
또 하나의 문제 : 작업을 제어하는 것은 어렵습니다! 작업을 생성하고 제출하는 하나의 관리자 스레드와 작업 대기열이있는 두 개의 작업 스레드 (ExecutorService 사용)를 갖는 것이 좋습니다.
여기서 핵심 포인트 만 다루고 있습니다. 멀티 스레드 프로그래밍은 많은 전문가들에 의해 가장 어려운 프로그래밍 주제 중 하나로 간주됩니다. 직관적이지 않고 복잡하며 추상화가 약한 경우가 많습니다.
편집-ExecutorService를 사용한 예 :
public class TaskThreader {
class DoStuff implements Callable {
Object in;
public Object call(){
in = doStep1(in);
in = doStep2(in);
in = doStep3(in);
return in;
}
public DoStuff(Object input){
in = input;
}
}
public abstract Object doStep1(Object input);
public abstract Object doStep2(Object input);
public abstract Object doStep3(Object input);
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
ArrayList<Callable> tasks = new ArrayList<Callable>();
for(Object input : inputs){
tasks.add(new DoStuff(input));
}
List<Future> results = exec.invokeAll(tasks);
exec.shutdown();
for(Future f : results) {
write(f.get());
}
}
}
첫째, 프로그램이 다중 코어 에서 더 빠르게 실행된다는 것을 스스로 증명해야합니다 . 많은 운영 체제는 가능한 한 동일한 코어에서 프로그램 스레드를 실행하기 위해 노력 합니다 .
동일한 코어에서 실행하면 많은 이점이 있습니다. CPU 캐시가 뜨겁습니다. 즉, 해당 프로그램에 대한 데이터가 CPU로로드됩니다. 잠금 / 모니터 / 동기화 개체는 CPU 캐시에 있으므로 다른 CPU가 버스 전체에서 캐시 동기화 작업을 수행 할 필요가 없습니다 (비싸다!).
프로그램을 항상 동일한 CPU에서 매우 쉽게 실행할 수있는 한 가지는 잠금 및 공유 메모리의 과도한 사용입니다. 스레드가 서로 대화해서는 안됩니다. 스레드가 동일한 메모리에서 동일한 객체를 사용하는 빈도가 적을수록 다른 CPU에서 더 자주 실행됩니다. 동일한 메모리를 더 자주 사용할수록 다른 스레드를 기다리는 것을 더 자주 차단해야합니다.
OS가 다른 스레드에 대한 하나의 스레드 블록을 볼 때마다 가능한 한 동일한 CPU에서 해당 스레드를 실행합니다. CPU 간 버스를 통해 이동하는 메모리 양을 줄입니다. 그것이 내가 당신의 프로그램에서 보는 원인이라고 생각합니다.
먼저 Brian Goetz의 "Concurrency in Practice"를 읽는 것이 좋습니다 .
이것은 동시 자바 프로그래밍을 설명하는 최고의 책입니다.
Concurrency is 'easy to learn, difficult to master'. I'd suggest reading plenty about the subject before attempting it. It's very easy to get a multi-threaded program to work correctly 99.9% of the time, and fail 0.1%. However, here are some tips to get you started:
There are two common ways to make a program use more than one core:
- Make the program run using multiple processes. An example is Apache compiled with the Pre-Fork MPM, which assigns requests to child processes. In a multi-process program, memory is not shared by default. However, you can map sections of shared memory across processes. Apache does this with it's 'scoreboard'.
- Make the program multi-threaded. In a multi-threaded program, all heap memory is shared by default. Each thread still has it's own stack, but can access any part of the heap. Typically, most Java programs are multi-threaded, and not multi-process.
At the lowest level, one can create and destroy threads. Java makes it easy to create threads in a portable cross platform manner.
As it tends to get expensive to create and destroy threads all the time, Java now includes Executors to create re-usable thread pools. Tasks can be assigned to the executors, and the result can be retrieved via a Future object.
Typically, one has a task which can be divided into smaller tasks, but the end results need to be brought back together. For example, with a merge sort, one can divide the list into smaller and smaller parts, until one has every core doing the sorting. However, as each sublist is sorted, it needs to be merged in order to get the final sorted list. Since this is "divide-and-conquer" issue is fairly common, there is a JSR framework which can handle the underlying distribution and joining. This framework will likely be included in Java 7.
There is no way to set CPU affinity in Java. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4234402
If you have to do it, use JNI to create native threads and set their affinity.
You should write your program to do its work in the form of a lot of Callable's handed to an ExecutorService and executed with invokeAll(...).
You can then choose a suitable implementation at runtime from the Executors class. A suggestion would be to call Executors.newFixedThreadPool() with a number roughly corresponding to the number of cpu cores to keep busy.
The easiest thing to do is break your program into multiple processes. The OS will allocate them across the cores.
Somewhat harder is to break your program into multiple threads and trust the JVM to allocate them properly. This is -- generally -- what people do to make use of available hardware.
Edit
How can a multi-processing program be "easier"? Here's a step in a pipeline.
public class SomeStep {
public static void main( String args[] ) {
BufferedReader stdin= new BufferedReader( System.in );
BufferedWriter stdout= new BufferedWriter( System.out );
String line= stdin.readLine();
while( line != null ) {
// process line, writing to stdout
line = stdin.readLine();
}
}
}
Each step in the pipeline is similarly structured. 9 lines of overhead for whatever processing is included.
This may not be the absolute most efficient. But it's very easy.
The overall structure of your concurrent processes is not a JVM problem. It's an OS problem, so use the shell.
java -cp pipline.jar FirstStep | java -cp pipline.jar SomeStep | java -cp pipline.jar LastStep
The only thing left is to work out some serialization for your data objects in the pipeline. Standard Serialization works well. Read http://java.sun.com/developer/technicalArticles/Programming/serialization/ for hints on how to serialize. You can replace the BufferedReader
and BufferedWriter
with ObjectInputStream
and ObjectOutputStream
to accomplish this.
I think this issue is related to Java Parallel Proccesing Framework (JPPF). Using this you can run diferent jobs on diferent processors.
JVM performance tuning has been mentioned before in Why does this Java code not utilize all CPU cores?. Note that this only applies to the JVM, so your application must already be using threads (and more or less "correctly" at that):
http://ch.sun.com/sunnews/events/2009/apr/adworkshop/pdf/5-1-Java-Performance.pdf
You can use below API from Executors with Java 8 version
public static ExecutorService newWorkStealingPool()
Creates a work-stealing thread pool using all available processors as its target parallelism level.
Due to work stealing mechanism, idle threads steal tasks from task queue of busy threads and overall throughput will increase.
From grepcode, implementation of newWorkStealingPool
is as follows
/**
* Creates a work-stealing thread pool using all
* {@link Runtime#availableProcessors available processors}
* as its target parallelism level.
* @return the newly created thread pool
* @see #newWorkStealingPool(int)
* @since 1.8
*/
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
'Development Tip' 카테고리의 다른 글
if 문에서 조건 평가 순서에 의존하는 것이 안전합니까? (0) | 2020.11.14 |
---|---|
C에서 순환 버퍼를 어떻게 구현합니까? (0) | 2020.11.14 |
캐럿 / 커서 위치를 문자열 값 WPF 텍스트 상자의 끝으로 설정 (0) | 2020.11.14 |
.live ()를 사용하여 jQuery UI 자동 완성 바인딩 (0) | 2020.11.14 |
Java 프로그램 내에서 방금 시작한 프로세스의 PID를 얻는 방법은 무엇입니까? (0) | 2020.11.14 |