|
| 1 | +package dev.suresh.qos |
| 2 | + |
| 3 | +import dev.suresh.downcallHandle |
| 4 | +import java.lang.foreign.FunctionDescriptor |
| 5 | +import java.lang.foreign.ValueLayout.JAVA_INT |
| 6 | +import java.util.concurrent.Executors |
| 7 | +import java.util.concurrent.SynchronousQueue |
| 8 | +import java.util.concurrent.ThreadFactory |
| 9 | +import java.util.concurrent.ThreadPoolExecutor |
| 10 | +import java.util.concurrent.TimeUnit |
| 11 | +import kotlinx.coroutines.asCoroutineDispatcher |
| 12 | + |
| 13 | +/** Constrain JVM threads and coroutines to the efficiency cores available on M-series Macs */ |
| 14 | +object FFMQosSetter { |
| 15 | + fun setQosClass(qosClass: QosClass, relativePriority: Int = 0): Int { |
| 16 | + val result = |
| 17 | + downcallHandle( |
| 18 | + "pthread_set_qos_class_self_np", |
| 19 | + FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT)) |
| 20 | + ?.invokeExact(qosClass.raw, relativePriority) as Int |
| 21 | + |
| 22 | + when (result != 0) { |
| 23 | + true -> System.err.println("Failed to set QoS class, error code: $result") |
| 24 | + else -> println("QoS class set successfully.") |
| 25 | + } |
| 26 | + return result |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +/** |
| 31 | + * Refer to the |
| 32 | + * [Energy Efficiency Guide for Mac Apps](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/PrioritizeWorkAtTheTaskLevel.html#//apple_ref/doc/uid/TP40013929-CH35-SW5) |
| 33 | + */ |
| 34 | +enum class QosClass(val raw: Int) { |
| 35 | + QOS_CLASS_USER_INTERACTIVE(0x21), |
| 36 | + QOS_CLASS_USER_INITIATED(0x19), |
| 37 | + QOS_CLASS_DEFAULT(0x15), |
| 38 | + QOS_CLASS_UTILITY(0x11), |
| 39 | + QOS_CLASS_BACKGROUND(0x09), |
| 40 | + QOS_CLASS_UNSPECIFIED(0x00), |
| 41 | +} |
| 42 | + |
| 43 | +val BackgroundQosCoroutineDispatcher by lazy { |
| 44 | + ThreadPoolExecutor( |
| 45 | + /* corePoolSize = */ 0, |
| 46 | + /* maximumPoolSize = */ Runtime.getRuntime().availableProcessors(), |
| 47 | + /* keepAliveTime = */ 60L, |
| 48 | + /* unit = */ TimeUnit.SECONDS, |
| 49 | + /* workQueue = */ SynchronousQueue(), |
| 50 | + /* threadFactory = */ object : ThreadFactory { |
| 51 | + |
| 52 | + override fun newThread(r: Runnable): Thread { |
| 53 | + val fact = Executors.defaultThreadFactory() |
| 54 | + return fact.newThread(r).apply { |
| 55 | + FFMQosSetter.setQosClass(QosClass.QOS_CLASS_BACKGROUND) |
| 56 | + name = "QosThreadPool-${threadId()}" |
| 57 | + } |
| 58 | + } |
| 59 | + }) |
| 60 | + .asCoroutineDispatcher() |
| 61 | +} |
0 commit comments