การใช้ CPU ต่ำเกินไปของ Java Application แบบหลายเธรดบน Windows


18

ฉันกำลังทำงานกับแอปพลิเคชัน Java เพื่อแก้ปัญหาการเพิ่มประสิทธิภาพเชิงตัวเลข - ปัญหาการเขียนโปรแกรมเชิงเส้นขนาดใหญ่ให้แม่นยำยิ่งขึ้น ปัญหาเดียวสามารถแบ่งออกเป็นปัญหาย่อยที่เล็กกว่าที่สามารถแก้ไขได้ในแบบคู่ขนาน เนื่องจากมีปัญหาย่อยมากกว่าคอร์ CPU ฉันจึงใช้ ExecutorService และกำหนดแต่ละปัญหาย่อยเป็น Callable ที่ได้รับการส่งไปยัง ExecutorService การแก้ปัญหาย่อยต้องเรียกไลบรารี่เนทีฟซึ่งเป็นตัวแก้ปัญหาการโปรแกรมเชิงเส้นในกรณีนี้

ปัญหา

ฉันสามารถรันแอพพลิเคชั่นบน Unix และระบบ Windows ที่มีฟิสิคัลคอร์สูงสุด 44 คอร์และหน่วยความจำสูงสุด 256g แต่เวลาในการคำนวณบนวินโดวส์นั้นมีลำดับความสำคัญสูงกว่าบน Linux สำหรับปัญหาใหญ่ Windows ไม่เพียง แต่ต้องใช้หน่วยความจำเพิ่มมากขึ้น แต่การใช้งาน CPU ในช่วงเวลานั้นลดลงจาก 25% ในช่วงเริ่มต้นเป็น 5% หลังจากผ่านไปสองสามชั่วโมง นี่คือภาพหน้าจอของตัวจัดการงานใน Windows:

การใช้งาน CPU ของ Task Manager

ข้อสังเกต

  • เวลาในการแก้ปัญหาสำหรับอินสแตนซ์ขนาดใหญ่ของปัญหาโดยรวมอยู่ในช่วงชั่วโมงต่อวันและใช้หน่วยความจำมากถึง 32 กรัม (บน Unix) เวลาแก้ปัญหาสำหรับปัญหาย่อยอยู่ในช่วง ms
  • ฉันไม่พบปัญหานี้ในปัญหาเล็ก ๆ ที่ใช้เวลาเพียงไม่กี่นาทีในการแก้ไข
  • Linux ใช้ทั้งสองซ็อกเก็ตนอกกรอบในขณะที่ Windows ต้องการให้ฉันเปิดใช้งานหน่วยความจำ interleaving ใน BIOS อย่างชัดเจนเพื่อให้แอปพลิเคชันใช้ประโยชน์จากทั้งสองคอร์ ไม่ว่าฉันจะทำสิ่งนี้หรือไม่ไม่มีผลต่อการเสื่อมสภาพของการใช้งาน CPU โดยรวมเมื่อเวลาผ่านไป
  • เมื่อฉันดูเธรดใน VisualVM เธรดพูลทั้งหมดกำลังทำงานไม่มีใครรอหรืออย่างอื่น
  • ตาม VisualVM เวลา CPU 90% ใช้ในการเรียกใช้ฟังก์ชันดั้งเดิม (การแก้ปัญหาโปรแกรมเชิงเส้นขนาดเล็ก)
  • การรวบรวมขยะไม่ใช่ปัญหาเนื่องจากแอปพลิเคชันไม่ได้สร้างและอ้างอิงวัตถุจำนวนมาก นอกจากนี้หน่วยความจำส่วนใหญ่ดูเหมือนว่าจะถูกจัดสรรออกนอกกอง ฮีป 4 กรัมเพียงพอบน Linux และ 8g บน Windows สำหรับอินสแตนซ์ที่ใหญ่ที่สุด

สิ่งที่ฉันได้ลอง

  • JVM args ทุกประเภท, XMS สูง, metaspace สูง, ธง UseNUMA, GC อื่น ๆ
  • JVMs ที่แตกต่างกัน (Hotspot 8, 9, 10, 11)
  • ไลบรารี่ต่าง ๆ ของตัวแก้ปัญหาการโปรแกรมเชิงเส้น (CLP, Xpress, Cplex, Gurobi)

คำถาม

  • อะไรที่ทำให้ความแตกต่างด้านประสิทธิภาพระหว่าง Linux และ Windows ของแอพพลิเคชั่น Java แบบมัลติเธรดขนาดใหญ่ที่ใช้การโทรแบบเนทีฟจำนวนมาก
  • มีสิ่งใดบ้างที่ฉันสามารถเปลี่ยนแปลงได้ในการใช้งานที่จะช่วย Windows เช่นฉันควรหลีกเลี่ยงการใช้ ExecutorService ที่ได้รับ Callables นับพันและทำสิ่งใดแทน

คุณได้ลองForkJoinPoolแทนExecutorService? การใช้ CPU 25% นั้นต่ำมากหากปัญหาของคุณเกิดจาก CPU
Karol Dowbecki

1
ปัญหาของคุณดูเหมือนสิ่งที่ควรผลักดัน CPU ไปที่ 100% และคุณอยู่ที่ 25% สำหรับปัญหาบางอย่างForkJoinPoolมีประสิทธิภาพมากกว่าการตั้งเวลาด้วยตนเอง
Karol Dowbecki

2
ขี่จักรยานผ่านฮอตสปอตเวอร์ชันคุณแน่ใจหรือไม่ว่าคุณกำลังใช้เซิร์ฟเวอร์ "และไม่ใช่" ลูกค้า " การใช้งาน CPU ของคุณบน Linux คืออะไร? นอกจากนี้ Windows uptime หลายวันก็น่าประทับใจ! ความลับของคุณคืออะไร? : P
erickson

3
อาจจะลองใช้Xperfเพื่อสร้างFlameGraph นี่อาจทำให้คุณเข้าใจว่า CPU กำลังทำอะไร (หวังว่าทั้งผู้ใช้และโหมดเคอร์เนล) แต่ฉันไม่เคยทำบน Windows
Karol Dowbecki

1
@ ไม่มีทั้งสองทำงาน (unix / win) ใช้อินเทอร์เฟซเดียวกันเพื่อเรียกใช้ไลบรารีเนทีฟหรือไม่ ฉันถามเพราะมันดูเหมือนแตกต่างกัน ไลค์: win ใช้ jna, linux jni
SR

คำตอบ:


2

สำหรับ Windows จำนวนเธรดต่อกระบวนการถูก จำกัด โดยพื้นที่ที่อยู่ของกระบวนการ (โปรดดูMark Markinovich - การผลักดันขีด จำกัด ของ Windows: กระบวนการและเธรด ) คิดว่านี่เป็นสาเหตุของผลข้างเคียงเมื่อใกล้ถึงขีด จำกัด (ชะลอการสลับบริบท, การแยกส่วน ... ) สำหรับ Windows ฉันจะพยายามแบ่งภาระงานเป็นชุดของกระบวนการ สำหรับปัญหาที่คล้ายกันว่าผมมีปีที่ผ่านมาผมดำเนินการ Java ห้องสมุดจะทำเช่นนี้ได้สะดวกยิ่งขึ้น (Java 8) มีลักษณะที่คุณต้องการ: ห้องสมุดให้กับงานวางไข่ในกระบวนการภายนอก


มันดูน่าสนใจมาก! ฉันลังเลที่จะไปไกลขนาดนี้ด้วยเหตุผลสองประการ: 1) จะมีค่าใช้จ่ายในการปฏิบัติงานเป็นอันดับและส่งวัตถุผ่านซ็อกเก็ต; 2) ถ้าฉันต้องการซีเรียลไลซ์ทุกอย่างนี้รวมถึงการพึ่งพาทั้งหมดที่เชื่อมโยงในงาน - มันจะเป็นงานที่ต้องเขียนรหัสใหม่ - อย่างไรก็ตามขอขอบคุณสำหรับลิงค์ที่มีประโยชน์
นิลส์

ฉันแบ่งปันความกังวลของคุณอย่างเต็มที่และออกแบบรหัสใหม่จะเป็นความพยายาม ในขณะที่สำรวจกราฟคุณจะต้องแนะนำเกณฑ์สำหรับจำนวนเธรดเมื่อถึงเวลาที่ต้องแบ่งงานออกเป็นกระบวนการย่อยใหม่ ในการระบุที่อยู่ 2) ให้ดูที่ไฟล์ที่แมปหน่วยความจำ Java (java.nio.MappedByteBuffer) โดยที่คุณสามารถแบ่งปันข้อมูลระหว่างกระบวนการได้อย่างมีประสิทธิภาพตัวอย่างเช่นข้อมูลกราฟของคุณ Godspeed :)
geri

0

เสียงเหมือน windows กำลังแคชหน่วยความจำบางส่วนไปยัง pagefile หลังจากไม่ถูกแตะต้องมาระยะหนึ่งแล้วนั่นเป็นสาเหตุที่ทำให้ CPU มีปัญหาคอขวดโดยความเร็วของดิสก์

คุณสามารถตรวจสอบด้วย Process explorer และตรวจสอบจำนวนหน่วยความจำแคช


คุณคิดว่า? มีหน่วยความจำว่างเพียงพอ เหตุใด Windows จึงเริ่มสลับเปลี่ยน ยังไงก็ตามขอบคุณ
นิลส์

อย่างน้อยในหน้าต่างแล็ปท็อปของฉันบางครั้งการแลกเปลี่ยนแอพพลิเคชั่นลดลงแม้จะมีหน่วยความจำเพียงพอ
Jew

0

ฉันคิดว่าประสิทธิภาพที่แตกต่างนี้เกิดจากการที่ OS จัดการเธรด JVM ซ่อนความแตกต่างของระบบปฏิบัติการทั้งหมด มีเว็บไซต์จำนวนมากที่คุณสามารถอ่านเกี่ยวกับเรื่องนี้เหมือนนี้ยกตัวอย่างเช่น แต่มันไม่ได้หมายความว่าความแตกต่างจะหายไป

ฉันคิดว่าคุณกำลังรันบน Java 8+ JVM ด้วยเหตุนี้ฉันขอแนะนำให้คุณลองใช้คุณสมบัติการสตรีมและฟังก์ชั่นการเขียนโปรแกรม การเขียนโปรแกรมฟังก์ชั่นนั้นมีประโยชน์มากเมื่อคุณมีปัญหาเล็ก ๆ น้อย ๆ มากมายและคุณต้องการเปลี่ยนจากการทำงานแบบต่อเนื่องเป็นแบบขนาน ข่าวดีก็คือคุณไม่จำเป็นต้องกำหนดนโยบายเพื่อกำหนดจำนวนเธรดที่คุณต้องจัดการ (เช่นเดียวกับ ExecutorService) ตัวอย่างเช่น (นำมาจากที่นี่ ):

package com.mkyong.java8;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class ParallelExample4 {

    public static void main(String[] args) {

        long count = Stream.iterate(0, n -> n + 1)
                .limit(1_000_000)
                //.parallel()   with this 23s, without this 1m 10s
                .filter(ParallelExample4::isPrime)
                .peek(x -> System.out.format("%s\t", x))
                .count();

        System.out.println("\nTotal: " + count);

    }

    public static boolean isPrime(int number) {
        if (number <= 1) return false;
        return !IntStream.rangeClosed(2, number / 2).anyMatch(i -> number % i == 0);
    }

}

ผลลัพธ์:

สำหรับสตรีมปกติจะใช้เวลา 1 นาที 10 วินาที สำหรับสตรีมแบบขนานจะใช้เวลา 23 วินาที PS ทดสอบกับ i7-7700, 16G RAM, WIndows 10

ดังนั้นฉันขอแนะนำให้คุณอ่านเกี่ยวกับการเขียนโปรแกรมฟังก์ชั่นสตรีมฟังก์ชั่นแลมบ์ดาใน Java และลองใช้การทดสอบจำนวนเล็กน้อยกับโค้ดของคุณ (ดัดแปลงให้ทำงานในบริบทใหม่นี้)


ฉันใช้กระแสข้อมูลในส่วนอื่น ๆ ของซอฟต์แวร์ แต่ในกรณีนี้งานจะถูกสร้างขึ้นในขณะที่ผ่านกราฟ ฉันจะไม่ทราบวิธีการห่อนี้โดยใช้กระแส
นิลส์

คุณสามารถสำรวจกราฟสร้างรายการแล้วใช้สตรีมได้หรือไม่
xcesco

ลำธารแบบขนานนั้นเป็นเพียงน้ำตาลเชิงซ้อนสำหรับ ForkJoinPool ที่ฉันได้ลอง (ดูความคิดเห็น @KarolDowbecki ด้านบน)
นิลส์

0

คุณช่วยโพสต์สถิติของระบบได้ไหม ตัวจัดการงานดีพอที่จะให้เบาะแสบางอย่างหากนั่นเป็นเครื่องมือเดียวที่มีให้ สามารถบอกได้อย่างง่ายดายว่างานของคุณกำลังรอคอย IO หรือไม่ซึ่งดูเหมือนว่าผู้ร้ายจะเป็นไปตามที่คุณอธิบาย อาจเกิดจากปัญหาการจัดการหน่วยความจำบางอย่างหรือไลบรารีอาจเขียนข้อมูลชั่วคราวบางส่วนลงดิสก์ ฯลฯ

เมื่อคุณพูดถึง 25% ของการใช้ CPU คุณหมายถึงมีเพียงไม่กี่คอร์เท่านั้นที่ทำงานในเวลาเดียวกันหรือไม่? (อาจเป็นไปได้ว่าแกนประมวลผลทั้งหมดทำงานเป็นครั้งคราว แต่ไม่พร้อมกัน) คุณจะตรวจสอบว่ามีการสร้างเธรด (หรือกระบวนการ) จำนวนมากในระบบจริงหรือไม่ จำนวนนั้นใหญ่กว่าจำนวนแกนเสมอหรือไม่?

หากมีเธรดเพียงพอมีหลายคนที่ไม่ได้ทำงานรอบางอย่างใช่ไหม หากเป็นจริงคุณสามารถลองขัดจังหวะ (หรือแนบดีบักเกอร์) เพื่อดูว่าพวกเขากำลังรออะไรอยู่


ฉันได้เพิ่มสกรีนช็อตของตัวจัดการงานสำหรับการดำเนินการที่เป็นตัวแทนของปัญหานี้ แอ็พพลิเคชันเองสร้างเธรดจำนวนมากเท่าที่มีคอร์ฟิสิคัลบนเครื่อง Java มีส่วนร่วมมากกว่า 50 เธรดในรูปนั้น ตามที่ได้กล่าวไปแล้ว VisualVM กล่าวว่าชุดข้อความทั้งหมดไม่ว่าง (สีเขียว) พวกเขาเพียงแค่ไม่ดัน CPU ให้ถึงขีด จำกัด บน Windows พวกเขาทำบน Linux
นิลส์

@ ไม่มีฉันสงสัยว่าคุณไม่ได้มีหัวข้อทั้งหมดที่ไม่ว่างในเวลาเดียวกันแต่จริงๆเพียง 9 - 10 ของพวกเขา มีการกำหนดตารางเวลาแบบสุ่มในทุกคอร์ดังนั้นคุณจึงมีการใช้งานเฉลี่ย 9/44 = 20% คุณสามารถใช้ Java threads โดยตรงแทนที่จะใช้ ExecutorService เพื่อดูความแตกต่างได้หรือไม่? ไม่ใช่เรื่องยากที่จะสร้าง 44 เธรดและแต่ละรายการจะเรียกใช้ Runnable / Callable จากกลุ่มงาน / คิว (แม้ว่า VisualVM จะแสดงหัวข้อ Java ทั้งหมดไม่ว่าง แต่ความเป็นจริงอาจเป็นไปได้ว่า 44 เธรดมีการกำหนดเวลาไว้อย่างรวดเร็วเพื่อให้ทุกคนมีโอกาสที่จะทำงานในช่วงการสุ่มตัวอย่างของ VisualVM)
Xiao-Feng Li

นั่นเป็นความคิดและบางสิ่งที่ฉันทำจริง ๆ ในบางจุด ในการใช้งานของฉันฉันยังทำให้แน่ใจว่าการเข้าถึงแบบเนทีฟเป็นแบบโลคัลสำหรับแต่ละเธรด แต่สิ่งนี้ไม่ได้สร้างความแตกต่างเลย
นิลส์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.