JavaScript 병행-네트워크 관계자 설명
37168 단어 webwebworkersjavascript
TL;박사 01 명
예시 프로그램
예를 들어, 우리는 웹 응용 프로그램을 구축하려고 한다. 이 프로그램은 표를 구성하는데, 항목마다 그 프로그램에 속하는 숫자가 prime인지 여부를 나타낸다.
우리는 ArrayBuffer을 사용하여 우리의 브리 값을 보존할 것이다. 우리는 대담하게 그것을 10메가바이트 크기로 설정할 것이다.
현재, 이것은 단지 우리의 스크립트로 하여금 힘든 일을 하게 할 뿐이다. 이것은 매우 유용한 일이 아니지만, 나는 미래의 게시물에서 여기에 기술된 기술을 사용하여 서로 다른 종류의 이진 데이터 (예를 들어 이미지, 오디오, 동영상) 를 처리할 것이다.
여기서 우리는 매우 간단한 알고리즘을 사용할 것이다. (더 좋은 알고리즘을 사용할 수 있다.)
function isPrime(candidate) {
for(var n=2; n <= Math.floor(Math.sqrt(candidate)); n++) {
// if the candidate can be divided by n without remainder it is not prime
if(candidate % n === 0) return false
}
// candidate is not divisible by any potential prime factor so it is prime
return true
}
다음은 우리 프로그램의 나머지 부분이다.색인html
<!doctype html>
<html>
<head>
<style>
/* make the page scrollable */
body {
height: 300%;
height: 300vh;
}
</style>
<body>
<button>Run test</button>
<script src="app.js"></script>
</body>
</html>
페이지를 스크롤하여 바로 JavaScript 코드의 효과를 볼 수 있습니다.응용 프로그램.js
document.querySelector('button').addEventListener('click', runTest)
function runTest() {
var buffer = new ArrayBuffer(1024 * 1024 * 10) // reserves 10 MB
var view = new Uint8Array(buffer) // view the buffer as bytes
var numPrimes = 0
performance.mark('testStart')
for(var i=0; i<view.length;i++) {
var primeCandidate = i+2 // 2 is the smalles prime number
var result = isPrime(primeCandidate)
if(result) numPrimes++
view[i] = result
}
performance.mark('testEnd')
performance.measure('runTest', 'testStart', 'testEnd')
var timeTaken = performance.getEntriesByName('runTest')[0].duration
alert(`Done. Found ${numPrimes} primes in ${timeTaken} ms`)
console.log(numPrimes, view)
}
function isPrime(candidate) {
for(var n=2; n <= Math.floor(Math.sqrt(candidate)); n++) {
if(candidate % n === 0) return false
}
return true
}
우리는 User Timing API을 사용하여 시간을 측정하고 우리의 정보를 시간선에 추가합니다.이제 신뢰하는 오래된 Nexus 7(2013)에서 테스트를 실행하도록 하겠습니다.
그래, 이것은 그다지 인상적이지 않다, 그렇지?
더 심각한 것은 39초 동안 사이트가 어떤 것에 대한 반응도 멈추었다는 것이다. 스크롤, 클릭, 입력도 없었다.이 페이지는 동결되었다.
이는 JavaScript가 단일 스레드이고 하나의 스레드에서 한 가지 일만 동시에 발생할 수 있기 때문입니다.더 심각한 것은 페이지의 상호작용과 관련된 거의 모든 내용 (따라서 스크롤, 텍스트 입력 등에 사용되는 브라우저 코드) 이 같은 라인에서 실행된다는 것이다.
설마 우리는 어떤 번거로운 일도 할 수 없단 말인가?
인터넷 종사자가 와서 구조하다
아니오, 이것이 바로 우리가 Web Workers을 사용할 수 있는 일입니다.
Web Worker는 JavaScript 파일로 웹 응용 프로그램 원본과 동일하며 별도의 스레드에서 실행됩니다.
개별 스레드에서 실행된다는 것은 다음과 같습니다.
그렇다면 주요 검색 작업이 진행될 때, 우리는 어떻게 페이지의 응답성을 유지합니까?다음은 프로그램입니다.
이 노동자는 자신의 일을 한다
다음은 업데이트된 코드입니다.
응용 프로그램.js
document.querySelector('button').addEventListener('click', runTest)
function runTest() {
var buffer = new ArrayBuffer(1024 * 1024 * 10) // reserves 10 MB
var view = new Uint8Array(buffer) // view the buffer as bytes
performance.mark('testStart')
var worker = new Worker('prime-worker.js')
worker.onmessage = function(msg) {
performance.mark('testEnd')
performance.measure('runTest', 'testStart', 'testEnd')
var timeTaken = performance.getEntriesByName('runTest')[0].duration
view.set(new Uint8Array(buffer), 0)
alert(`Done. Found ${msg.data.numPrimes} primes in ${timeTaken} ms`)
console.log(msg.data.numPrimes, view)
}
worker.postMessage(buffer)
}
제1노동자.js
self.onmessage = function(msg) {
var view = new Uint8Array(msg.data),
numPrimes = 0
for(var i=0; i<view.length;i++) {
var primeCandidate = i+2 // 2 is the smalles prime number
var result = isPrime(primeCandidate)
if(result) numPrimes++
view[i] = result
}
self.postMessage({
buffer: view.buffer,
numPrimes: numPrimes
})
}
function isPrime(candidate) {
for(var n=2; n <= Math.floor(Math.sqrt(candidate)); n++) {
if(candidate % n === 0) return false
}
return true
}
다음은 Nexus 7을 다시 실행할 때 얻은 결과입니다.그럼, 응, 모든 의식이 우리에게 무엇을 주었니?어쨌든 지금은 얘가 더 느려졌어!
이곳의 대승리는 그것을 더 빨리 만들지는 않았지만, 페이지를 스크롤하거나 다른 방식으로 상호작용을 시도해 보았다.그것은 시종일관 응답을 유지한다.계산이 자신의 라인으로 옮겨졌기 때문에, 우리는 주 라인 처리가 사용자에 대한 응답을 방해하지 않을 것입니다.
그러나 우리가 더욱 속도를 내기 전에, 우리는
navigator.hardwareConcurrency
이 어떻게 일을 하는지 중요한 세부 사항을 이해할 것이다.클론 비용
앞에서 말한 바와 같이, 주 라인과 작업 라인은 분리되어 있기 때문에, 우리는 메시지를 사용하여 그들 사이에서 데이터를 전달해야 한다
그러나 이것은 실제로 어떻게 그것들 사이에서 데이터를 이동합니까?우리가 이전에 한 방법의 답은 structured cloning이었다.
이것은 우리가 10메가바이트의 Array Buffer를 일꾼에게 복제한 다음에 일꾼에게서 Array Buffer를 복제해야 한다는 것을 의미한다.
나는 이것이 총 30MB의 메모리를 차지할 것이라고 가정한다. 10개는 원시적인 Array Buffer에서, 10개는 워크맨에게 보내는 복사본에서, 나머지 10개는 보내는 복사본에서.
다음은 테스트를 시작하기 전의 메모리 사용량입니다.
테스트 완료 후:
잠깐만, 아직 50메가바이트가 있어.사실 증명:
*) 복제가 아닌 타겟 스레드로 클론을 이동하는 이유는 잘 모르겠지만 시리얼화 자체가 예상치 못한 메모리 오버헤드를 초래하는 것 같습니다.
양도 가능한 설비는 시간을 절약할 수 있다
다행히도 선택할 수 있는 두 번째 매개 변수인
postMessage
에서는 서로 다른 방식으로 라인 사이에서 데이터를 전송하는데 이를 전송 목록이라고 부른다.두 번째 매개 변수는 클론에서 제외되고 이동되거나 전송되는 Transferable개의 객체 목록을 저장할 수 있습니다.
그러나 전송 대상은 소스 스레드에서 중화되기 때문에 예를 들어 Array Buffer는 보조 스레드로 전송된 후 주 스레드에 어떠한 데이터도 포함하지 않으며
postMessage
은 0이 됩니다.이것은 여러 라인이 공유 데이터에 접근할 때 일련의 문제를 처리하는 메커니즘을 실현해야 하는 비용을 피하기 위해서이다.
전송을 사용하여 조정된 코드는 다음과 같습니다.
응용 프로그램.js
worker.postMessage(buffer, [buffer])
제1노동자.js
self.postMessage({
buffer: view.buffer,
numPrimes: numPrimes
}, [view.buffer])
다음은 우리의 숫자입니다.그래서 우리는 복제 노동자보다 조금 빨라서 원시적인 주선 차단 버전에 가깝다.우리 기억력 어때요?
그래서 40mb부터 50mb가 넘으면 맞는 것 같아요.
일꾼이 많을수록 속도가 빨라진다고?
그래서 지금까지 저희가 이미...
저희도 속도를 낼 수 있을까요?
숫자의 범위와 버퍼를 여러 작업공간으로 분할하여 병렬로 실행하고 결과를 결합할 수 있습니다.
응용 프로그램.js
우리는 노동자를 한 명 내놓는 것이 아니라 네 명의 노동자를 내놓을 것이다.모든 노동자는 오프셋 시작과 검사할 숫자를 표시하는 메시지를 받을 것이다.
노동자 한 명이 일을 끝낼 때, 그것은
모든 직원이 완성되면 우리는 최종 결과를 나타낼 것이다.
document.querySelector('button').addEventListener('click', runTest)
function runTest() {
const TOTAL_NUMBERS = 1024 * 1024 * 10
const NUM_WORKERS = 4
var numbersToCheck = TOTAL_NUMBERS, primesFound = 0
var buffer = new ArrayBuffer(numbersToCheck) // reserves 10 MB
var view = new Uint8Array(buffer) // view the buffer as bytes
performance.mark('testStart')
var offset = 0
while(numbersToCheck) {
var blockLen = Math.min(numbersToCheck, TOTAL_NUMBERS / NUM_WORKERS)
var worker = new Worker('prime-worker.js')
worker.onmessage = function(msg) {
view.set(new Uint8Array(msg.data.buffer), msg.data.offset)
primesFound += msg.data.numPrimes
if(msg.data.offset + msg.data.length === buffer.byteLength) {
performance.mark('testEnd')
performance.measure('runTest', 'testStart', 'testEnd')
var timeTaken = performance.getEntriesByName('runTest')[0].duration
alert(`Done. Found ${primesFound} primes in ${timeTaken} ms`)
console.log(primesFound, view)
}
}
worker.postMessage({
offset: offset,
length: blockLen
})
numbersToCheck -= blockLen
offset += blockLen
}
}
제1노동자.js
작업 스레드는 주 스레드 정렬의
byteLength
바이트를 수용할 수 있는 Uint8Array 보기를 만듭니다.기본 체크는 필요한 오프셋에서 시작하여 데이터를 다시 전송합니다.
self.onmessage = function(msg) {
var view = new Uint8Array(msg.data.length),
numPrimes = 0
for(var i=0; i<msg.data.length;i++) {
var primeCandidate = i+msg.data.offset+2 // 2 is the smalles prime number
var result = isPrime(primeCandidate)
if(result) numPrimes++
view[i] = result
}
self.postMessage({
buffer: view.buffer,
numPrimes: numPrimes,
offset: msg.data.offset,
length: msg.data.length
}, [view.buffer])
}
function isPrime(candidate) {
for(var n=2; n <= Math.floor(Math.sqrt(candidate)); n++) {
if(candidate % n === 0) return false
}
return true
}
결과는 다음과 같습니다.따라서 이 해결 방안은 약 절반의 시간과 상당한 메모리 비용(40mb의 기본 메모리 사용량 +10mb의 목표 버퍼+4x2.5mb의 모든 작업공간 버퍼+2mb의 모든 작업공간 비용)을 썼다.
다음은 근로자 4명의 애플리케이션을 사용하는 일정입니다.
우리는 노동자들이 병행 운행하는 것을 볼 수 있지만, 우리는 4배의 속도를 얻지 못했다. 왜냐하면 어떤 노동자들은 다른 사람들보다 더 긴 시간을 필요로 하기 때문이다.이것은 우리가 숫자의 범위를 나누는 방식의 결과이다. 모든 노동자는 숫자
length
을 2에서 x
사이의 모든 숫자로 나누어야 하기 때문에 숫자가 큰 노동자일수록 더 많은 구분, 즉 더 많은 작업을 해야 한다.이것은 당연히 더욱 평균적으로 조작을 분배하는 방식으로 숫자를 나누어 최소화할 수 있다.나는 이 열성적인 독자에게 이것을 연습으로 남겨 줄 것이다.또 다른 문제는 우리가 더 많은 노동자를 참여시킬 수 있느냐는 것이다.
다음은 근로자 8명의 결과다.
됐어, 속도가 느려졌어!타임라인은 이러한 상황이 발생한 이유를 보여줍니다.
우리는 경미한 중첩을 제외하고는 4명의 노동자가 동시에 일하고 있다는 것을 발견했다.
이것은 시스템과 직원의 특징에 따라 결정될 것이며, 딱딱한 숫자가 아니다.
한 시스템이 같은 시간에 이렇게 많은 일을 할 수 밖에 없으며, 보통 입출력 제한 (즉 네트워크나 파일 처리량 제한) 이나 CPU 제한 (즉 CPU에서 계산을 실행하는 제한) 으로 일한다.
우리의 예에서, 모든 노동자들은 우리가 계산한 CPU를 차지한다.Nexus 7에는 4개의 코어가 있으므로 CPU를 완전히 사용하는 네 명의 작업자를 동시에 처리할 수 있습니다.
일반적으로 CPU 및 입출력(I/O)이 바인딩된 워크로드나 작은 워크로드에서 처리하기 어려운 문제가 발생할 수 있으므로 작업자의 수를 판단하기 어려울 수 있습니다.사용 가능한 논리 CPU 수를 알고 싶으면
√x
을 사용하십시오.마무리
이것은 상당히 많은 내용이니, 우리 한번 되돌아봅시다.
JavaScript는 단일 스레드이며 브라우저 작업과 동일한 스레드에서 실행되므로 UI를 신선하고 간결하게 유지할 수 있습니다.
그리고 웹 워커를 사용하여 우리의 작업을 다른 라인으로 옮기고'postMessage'를 사용합니다.스레드 간 통신.
우리는 라인이 무한히 확장되지 않을 것이라는 것을 알아차렸기 때문에, 우리가 운행하는 라인의 수량을 고려하는 것을 권장합니다.
우리가 이렇게 할 때, 우리는 기본적으로 데이터가 복제되는 것을 발견하는데, 이것은 육안으로 보는 것보다 더 많은 메모리 소모를 초래하기 쉽다.
우리는 데이터 전송을 통해 이 문제를 해결했는데 이것은 일부 유형의 데이터(Transferables)에 대해 실행 가능한 선택이다.
Reference
이 문제에 관하여(JavaScript 병행-네트워크 관계자 설명), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/g33konaut/javascript-in-parallel-web-workers-explained-5588텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)