Algoritmi Paraleli și Distribuiți - Introducere
40 Questions
0 Views

Choose a study mode

Play Quiz
Study Flashcards
Spaced Repetition
Chat to Lesson

Podcast

Play an AI-generated podcast conversation about this lesson

Questions and Answers

Care dintre următoarele descrie corect accesul la memorie în sistemele UMA?

  • Toate procesoarele accesează aceeași memorie partajată cu latență uniformă. (correct)
  • Memoria este distribuită, iar accesul depinde de locația procesorului.
  • Fiecare procesor are propria sa memorie privată.
  • Procesoarele accesează memoria printr-o rețea complexă.

Ce tip de arhitectură de memorie este specific sistemelor NUMA?

  • Memoria locală a fiecărui procesor.
  • Memoria accesată prin intermediul unui singur bus.
  • Memoria distribuită cu acces neuniform. (correct)
  • Memoria partajată cu acces uniform.

Care dintre următoarele topologii de rețea nu este menționată ca fiind utilizată în sistemele de calcul paralel?

  • Tablou
  • Stea (correct)
  • Cub
  • Arbore

Ce tip de paralelism permite executarea simultană a mai multor instrucțiuni pe diferite seturi de date?

<p>Paralelism la nivel de instrucțiune. (D)</p> Signup and view all the answers

Ce caracteristică definește multiprocesarea la nivel de task, spre deosebire de multithreading?

<p>Task-urile (procesele) pot comunica între ele. (A)</p> Signup and view all the answers

Care dintre următoarele NU este un beneficiu al calculului paralel și distribuit?

<p>Reducerea complexității algoritmilor. (B)</p> Signup and view all the answers

Care dintre următoarele afirmații despre variabilele atomice este adevărată ?

<p>Variabilele atomice garantează ca o operație de 64 biți va fi executată în întregime, chiar și pe un procesor de 32 biți. (C)</p> Signup and view all the answers

Care dintre următoarele poate fi considerat un mecanism de sincronizare ?

<p>Variabilele atomice (D), Semafoarele și semafoarele binare. (E)</p> Signup and view all the answers

Care dintre următoarele este un exemplu de condiție de cursă în contextul specificat?

<p>Două procese încearcă să acceseze simultan un cont bancar. (A), Două procese încearcă să acceseze simultan o structură de date. (B)</p> Signup and view all the answers

Ce este o secțiune critică?

<p>Un bloc de cod care este accesat de un singur proces la un moment dat. (C)</p> Signup and view all the answers

Ce rol au semafoarele în sincronizarea proceselor ?

<p>Semafoarele blochează procesele care încearcă să acceseze simultan resursele. (E)</p> Signup and view all the answers

Care este diferența dintre un semafor binar (Mutex) și un semafor generic?

<p>Semafoarele binare permit accesul doar unui singur proces la un moment dat, în timp ce semafoarele generice permit accesul mai multor. (B)</p> Signup and view all the answers

Care dintre următoarele opțiuni descrie corect funcționarea semaforului binar (Mutex) ?

<p>Mutex blochează accesul la o resursă partajată până când procesul care a obținut accesul la resursă o eliberează. (E)</p> Signup and view all the answers

Care dintre următoarele exemple este un exemplu de utilizare a barierei în sincronizarea proceselor ?

<p>Verificarea dacă toate procesele au terminat o sarcină specifică. (A)</p> Signup and view all the answers

Ce este o variabilă atomică?

<p>O variabilă care este modificată atomic, adică modificarea este indivizibilă și nu poate fi întreruptă de alte procese. (E)</p> Signup and view all the answers

Care dintre următoarele reprezintă o limitare a programării secvențiale legată de viteza de transmisie a datelor?

<p>Viteza luminii ca limită maximă (B)</p> Signup and view all the answers

Care dintre următoarele tehnologii este un exemplu de mediu de procesare paralelă și distribuită?

<p>Tehnologia Blockchain (D)</p> Signup and view all the answers

Ce tip de arhitectură de procesor este specifică unui sistem SISD (Single Instruction Stream, Single Data Stream)?

<p>Un calculator clasic one-core (C)</p> Signup and view all the answers

În contextul memoriei partajate, ce înseamnă termenul 'CREW'?

<p>Concurrent Read Exclusive Write (A)</p> Signup and view all the answers

Ce reprezintă acronimul MIMD în taxonomia lui Flynn?

<p>Multiple Instruction Streams, Multiple Data Streams (A)</p> Signup and view all the answers

Ce tip de memorie partajată permite ca o variabilă să fie citită într-un singur pas, dar necesită log(N) pași pentru a fi citită într-un alt context anume?

<p>CR – Concurrent Read (A)</p> Signup and view all the answers

Care dintre următoarele opțiuni descrie cel mai bine modul în care majoritatea aplicațiilor utilizează thread-uri în prezent?

<p>Folosesc mai multe thread-uri (B)</p> Signup and view all the answers

Ce semnifică termenul 'multi-core' în contextul procesoarelor moderne?

<p>Mai multe nuclee de procesare pe același chip (C)</p> Signup and view all the answers

Care dintre următoarele NU este o caracteristică comună a supercomputerelor?

<p>Număr redus de procesoare (D)</p> Signup and view all the answers

Ce concept descrie executarea mai multor task-uri simultan?

<p>Multitasking (D)</p> Signup and view all the answers

Care este o caracteristică principală a multi-threading-ului?

<p>Thread-urile împărtășesc memoria procesului (A)</p> Signup and view all the answers

Ce se întâmplă în cazul unei condiții de competiție?

<p>Valoarea finală poate varia în funcție de ordinea execuției (A)</p> Signup and view all the answers

Ce politică este utilizată pentru planificarea thread-urilor de către sistemul de operare?

<p>Timp esalonat (C)</p> Signup and view all the answers

Care dintre următoarele afirmații este adevărată despre hyperthreading?

<p>Crește numărul de thread-uri care pot rula simultan pe un procesor (C)</p> Signup and view all the answers

Poate un sistem de operare să suporte multitasking pe mai multe core-uri?

<p>Da, este posibil (D)</p> Signup and view all the answers

Cum se comportă thread-urile în cadrul unei aplicații multi-threaded?

<p>Thread-urile împărtășesc același bloc de control al procesului (A)</p> Signup and view all the answers

Ce reprezintă o condiție de competiție în multi-threading?

<p>O situație unde mai multe thread-uri accesează resurse comune simultan (C)</p> Signup and view all the answers

Care este valoarea finală a lui a dacă cele două thread-uri rulează simultan și fiecare adaugă 2 la a?

<p>4 (D)</p> Signup and view all the answers

Ce se va întâmpla cu variabila a dacă Thread 1 termină prima operațiune înainte de Thread 2?

<p>Va fi egal cu 2. (C)</p> Signup and view all the answers

Ce este un race condition în contextul thread-urilor?

<p>Un conflict între două sau mai multe thread-uri care accesează aceleași resurse. (C)</p> Signup and view all the answers

Dacă cele două thread-uri utilizează aceleași regiuni de memorie, ce se poate întâmpla cu rezultatul final al lui a?

<p>Rezultatul poate varia nelimitat între 0 și 4. (C)</p> Signup and view all the answers

Când ambele thread-uri încarcă aceeași valoare a lui a, ce se întâmplă cu registrele eax?

<p>Unul dintre registre este 0 iar celălalt este 2. (C)</p> Signup and view all the answers

Care este rezultatul executării thread-urilor asupra valorii a dacă unul dintre ele modifică a înainte de celălalt?

<p>Valoarea finală va fi influențată de ordinea de execuție. (A)</p> Signup and view all the answers

Ce strategie poate fi utilizată pentru a evita un race condition?

<p>Sincronizarea thread-urilor. (A)</p> Signup and view all the answers

Ce se poate deduce despre valorile eax în timpul execuției thread-urilor?

<p>Valorile eax pot fi diferite datorită execuției concurente. (B)</p> Signup and view all the answers

Flashcards

Acces la Memorie Uniform (UMA)

Un tip de memorie partajată în care toate procesoarele au acces egal și uniform la toate locațiile de memorie.

Acces la Memorie Non-Uniform (NUMA)

Un tip de memorie distribuită în care procesoarele nu au acces egal la toate locațiile de memorie. Unele locații pot fi accesate mai rapid decât altele.

Paralelism la nivel de instrucțiune

Un tip de paralelism care operează pe nivel de instrucțiune, permițând ca instrucțiunile să fie executate simultan.

Paralelism la nivel de task

Un tip de paralelism care permite execuția simultană a mai multor procese sau fire de execuție.

Signup and view all the flashcards

Multi-tasking

Un mod de a realiza paralelism la nivel de task în care fiecărui proces îi este alocată o anumită perioadă de timp pentru a rula, alternând între procese.

Signup and view all the flashcards

Programarea Paralelă

Un tip de programare care folosește mai multe procesoare sau nuclee pentru a executa o sarcină în paralel, accelerând procesul.

Signup and view all the flashcards

Programarea Distribuită

Un tip de programare în care resursele sunt distribuite pe mai multe noduri conectate în rețea, permițând procesarea distribuită și creșterea scalabilității.

Signup and view all the flashcards

Timp de execuție mai scurt

Reduce timpul necesar pentru a finaliza o sarcină.

Signup and view all the flashcards

Abordarea problemelor de dimensiuni mari

Permite abordarea unor probleme mari și complexe, care ar fi imposibil de rezolvat cu resurse limitate.

Signup and view all the flashcards

Accesul la resurse aflate la distanță

Permite accesarea resurselor aflate la distanță, cum ar fi servere sau dispozitive.

Signup and view all the flashcards

Reducerea costurilor

Reduce costurile prin optimizarea resurselor și utilizarea eficientă a hardware-ului.

Signup and view all the flashcards

Toleranță la defecte

Oferă reziliență la erori și defecțiuni, deoarece o parte din sistem poate continua să funcționeze chiar dacă alte părți sunt inactive.

Signup and view all the flashcards

Scalabilitate

Permite creșterea capacității sistemului prin adăugarea de resurse suplimentare, fără a afecta performanța.

Signup and view all the flashcards

Condiție de cursă

O situație în care rezultatul unei operații depinde de ordinea în care se execută multiple fire de execuție (thread-uri) care au acces la resurse partajate.

Signup and view all the flashcards

Conditiție de cursă

O condiție de cursă apare atunci când rezultatul unei operații depinde de ordinea în care se execută multiple thread-uri care accesează aceeași variabilă.

Signup and view all the flashcards

Conditiție de cursă

Într-o condiție de cursă, rezultatul operației poate varia în funcție de momentul în care fiecare thread accesează variabila.

Signup and view all the flashcards

Exemple de condiții de cursă

Exemple de condiții de cursă includ operații simple cum ar fi: incrementarea unei variabile, modificarea unei liste sau accesul la un fișier.

Signup and view all the flashcards

Rezultatul condiției de cursă

În cazul adunării variabilei "a" cu 2 de către două thread-uri, rezultatul final poate fi 4 sau 2, în funcție de momentul în care fiecare thread citește valoarea lui "a" din memorie și scrie noua valoare.

Signup and view all the flashcards

Prevenirea condițiilor de cursă

Pentru a preveni condițiile de cursă, se folosesc mecanisme de sincronizare.

Signup and view all the flashcards

Mecanisme de sincronizare

Exemple de mecanisme de sincronizare includ mutex-uri, semafoare și bariere.

Signup and view all the flashcards

Sincronizare - soluția

Folosind mecanisme de sincronizare, se elimină condiția de cursă, rezultatul operației devenind predictibil și consistent, indiferent de ordinea în care se execută thread-urile.

Signup and view all the flashcards

Legea lui Moore

Legea lui Moore se referă la observația că numărul de tranzistoare pe un cip integrat (circuit integrat) se dublează aproximativ la fiecare doi ani. Aceasta duce la îmbunătățiri semnificative ale performanței computerelor.

Signup and view all the flashcards

Care sunt limitările programării secvențiale?

Programările secvențiale se confruntă cu mai multe limitări, inclusiv viteza de transmisie a datelor care este limitată de viteza luminii, miniaturizarea componentelor, cu un tranzistor care ar putea ajunge la dimensiunea unui atom, precum și costuri enorme asociate cu cercetarea și dezvoltarea noilor procesoare.

Signup and view all the flashcards

Ce este CMOS?

CMOS (Complementary Metal-Oxide Semiconductor) este o tehnologie utilizată în majoritatea circuitelor integrate moderne. Este o tehnologie de proiectare care permite o eficiență energetică ridicată și o densitate mare de tranzistoare.

Signup and view all the flashcards

De ce sunt atât de importante cursurile de programare paralelă și distribuită?

Este crucial să înțelegem programarea paralelă și distribuită, deoarece tehnologiile bazate pe paralelizare sunt din ce în ce mai răspândite. De exemplu, blockchain, sisteme peer-to-peer, procesoarele multi-core se bazează pe paralelizare.

Signup and view all the flashcards

Ce este Taxonomia Flynn?

Taxonomia Flynn este o clasificare a arhitecturilor de calcul paralel, clasificând sistemele în funcție de fluxurile de instrucțiuni (IS) și de fluxurile de date (DS).

Signup and view all the flashcards

Ce este SISD?

SISD (Single Instruction Stream, Single Data Stream) este un model de calcul clasic, utilizat în majoritatea computerelor personale. Are un singur procesor care execută instrucțiuni pe un singur set de date.

Signup and view all the flashcards

Ce este SIMD?

SIMD (Single Instruction Stream, Multiple Data Streams) este un model de calcul care permite executarea aceleiași instrucțiuni pe multiple date simultan. Procesoarele GPU și extensiile SSE sunt exemple de implementări SIMD.

Signup and view all the flashcards

Ce este MISD?

MISD (Multiple Instruction Streams, Single Data Stream) se referă la sisteme specializate care execută instrucțiuni diferite pe același set de date. Este un model rar întâlnit.

Signup and view all the flashcards

Ce este MIMD?

MIMD (Multiple Instruction Streams, Multiple Data Streams) este un model de calcul care permite executarea instrucțiunilor diferite pe seturi de date diferite simultan. Majoritatea procesoarelor moderne (inclusiv procesoarele din telefoanele tale) utilizează MIMD.

Signup and view all the flashcards

Ce este memoria partajată?

Memoria partajată (shared memory) este un model de memorie în care mai multe procesoare pot accesa un singur spațiu de memorie. PRAM (Parallel Random Access Memory) este o arhitectură specifică care descrie relația dintre procesoare și memorie.

Signup and view all the flashcards

Ce este un thread?

Un thread este o unitate de execuție independentă în cadrul unui proces. Acesta partajează memoria cu alte thread-uri din același proces. Într-un proces, mai multe fire de execuție pot rula simultan. Acest concept este utilizat pentru a crește performanța aplicațiilor prin divizarea sarcinilor în unități mai mici, care pot fi executate în paralel.

Signup and view all the flashcards

Ce este multi-threading?

Multi-threading este un concept prin care se lansează mai multe thread-uri ale aceluiași proces. Aceste thread-uri se pot executa simultan pe același procesor sau pe procesoare diferite, crescând performanța aplicațiilor. Un task poate fi divizat în operații mai mici și se poate rula pe thread-uri separate, beneficiind de performanța procesorului.

Signup and view all the flashcards

Ce este multi-tasking?

Multi-tasking este un concept prin care un sistem de operare poate rula mai multe programe (de exemplu, aplicatii diferite, processes) simultan. Aceasta se realizează prin împărțirea timpului de procesor între programele care rulează.

Signup and view all the flashcards

Ce este o "condiție de cursă"?

O "condiție de cursă" apare atunci când rezultatul unei operații depinde de ordinea în care sunt executate mai multe thread-uri, putând duce la rezultate incorecte. De exemplu, dacă două thread-uri încearcă să modifice aceeași variabilă în același timp, rezultatul poate fi neașteptat. Este o problemă obișnuită în programarea multi-threaded.

Signup and view all the flashcards

Ce este Hyperthreading?

Hyperthreading este o tehnologie care permite unui singur procesor fizic să execute două fire de execuție (thread-uri) independent. Acesta permite procesorului să proceseze mai multe instrucțiuni simultan, crescând performanța sistemului.

Signup and view all the flashcards

Cum împart thread-urile memoria?

În multi-threading, thread-urile din cadrul aceluiași proces partajează resursele de memorie. Aceasta înseamnă că toate thread-urile au acces la aceleași date și cod, ceea ce poate fi de mare ajutor în optimizarea performanței.

Signup and view all the flashcards

Poate funcționa multi-tasking pe mai multe procesoare?

Multi-tasking poate fi realizat pe mai multe procesoare. De exemplu, dacă ai un computer cu 4 procesoare, poți rula 4 programe separate (de exemplu, 4 aplicatii) în paralel, cu fiecare procesor ocupându-se de un program separat.

Signup and view all the flashcards

Se pot executa mai multe thread-uri pe un singur core?

Da, se poate rula mai multe thread-uri pe un singur core. Fiecare core din procesor poate executa procesul în mod simultan utilizând thread-uri. Acest lucru permite utilizarea mai eficientă a resurselor de procesare.

Signup and view all the flashcards

Variabilă atomică

O variabilă care poate fi modificată în siguranță de mai multe fire de execuție concurente, fără a provoca erori datorate condițiilor de cursă.

Signup and view all the flashcards

Mutex

Un mecanism de sincronizare care permite accesul la o resursă partajată unui singur fir de execuție la un moment dat, asigurând că doar o singură operație critică poate fi executată pe resursa partajată la un moment dat.

Signup and view all the flashcards

Secțiune critică

Un bloc de cod care accesează o resursă partajată și trebuie executat în întregime fără a fi întrerupt de alte fire de execuție, prevenind apariția condițiilor de cursă.

Signup and view all the flashcards

Barieră

Un mecanism de sincronizare folosit la pornirea simultană a mai multor fire de execuție, asigurând că toate firele de execuție sunt sincronizate la un punct specific de pornire în executia lor.

Signup and view all the flashcards

Semafor

Un mecanism simplu de sincronizare care oferă o interfață pentru a proteja o resursă partajată împotriva accesului concurent, asigurând accesul simultan la resursă doar unui singur fir de execuție.

Signup and view all the flashcards

Semafor binar

Un semafor binar se comportă ca o variabilă booleană, permițând doar un singur proces să acceseze o resursă la un moment dat.

Signup and view all the flashcards

Atomicitate

O operație care trebuie executată în întregime, fără a fi întreruptă de alte operații, garantând că modificările aduse datelor sunt vizibile simultan tuturor firelor de execuție; este un concept vital pentru eliminarea condițiilor de cursă în programarea concurentă.

Signup and view all the flashcards

Operație atomică

Un set de instrucțiuni care asigură că operația se execută ca o singură unitate indivizibilă, fără a fi întreruptă de alte fire de execuție, asigurând astfel consistența datelor.

Signup and view all the flashcards

Vizibilitate

Un concept esențial în programarea concurentă, unde se asigură că modificările aduse unei variabile sunt vizibile simultan la toate firele de execuție, prevenind confuzii în accesarea simultană a aceleiași date.

Signup and view all the flashcards

Study Notes

Algoritmi Paraleli și Distribuiți - Introducere

  • Motivul programării paralele și distribuite:
    • Timpul de execuție mai scurt.
    • Abordarea problemelor de dimensiuni mari
    • Resurse accesibile aflate la distanţă.
    • Reducerea costurilor.
    • Toleranță la erori/defecte/probleme
    • Ascunderea timpilor de așteptare
    • Redundanţă
    • Scalabilitatea
    • Scaderea timpului de răspuns
    • Securitate îmbunătățită.
  • Informatii despre limitările programării secvențiale:
    • Viteza de transmisie este limitată de viteza luminii.
    • Miniaturizarea are o frontieră - tranzistor de dimensiunea unui atom. (A reda ceva la scară redusă)
    • Costurile de cercetare și proiectare sunt enorme.
  • Tehnologiile paralele și distribuite: Blockchain, Peer-to-Peer, Quad-Core.
  • Alte tehnologii disponibile ca suport in curs: Eclipse, Visual Studio.
  • Aplicatii distribuite: Dropbox, Spark, Boinc.
  • Aproape toate aplicațiile folosesc mai multe thread-uri.
  • Taxonomia Flynn clasifică arhitecturile de calcul în funcție de numărul de fluxuri de instrucțiuni și de date, având ca scop îmbunătățirea paralelismului și a procesării eficiente a informațiilor.
  • Noțiuni de bază:
    • SISD (Single Instruction Single Data) se referă la un model de arhitectură în care un singur procesor execută o singură instrucțiune pe un singur set de date.
      • Arhitectura von Neumann se referă la un model de arhitectură de calcul care integrează stocarea programelor și a datelor într-un singur sistem, optimizând performanța și eficiența procesării.
      • Calculatorul Clasic one-core
    • SIMD (Single Instruction Multiple Data) permite într-o singură instrucțiune procesarea mai multor elemente de date simultan, fiind eficient în procesarea vectorială.
      • Suportul SSE; procesoare GPU
    • MISD (Multiple Instruction Single Data) implică aplicarea mai multor instrucțiuni la același set de date.
      • Sisteme specializate
    • MIMD (Multiple Instruction Multiple Data) face posibilă executarea simultană a diferitelor instrucțiuni pe diferite seturi de date, adaptându-se bine la sarcini variate și complexitate crescută.
      • Procesoare actuale
  • Memoriile partajate sunt modalități prin care multiple procesore sau nuclee pot accesa aceeași zonă de memorie. În arhitectura Uniform Memory Access (UMA), fiecare procesor are același timp de acces la memorie, ceea ce oferă o realizare simetrică și eficientă a resurselor.
  • Memorie distribuita: Pe de altă parte, arhitectura Non-Uniform Memory Access (NUMA) permite ca procesorii să aibă timpi de acces diferiți la memoriile asociate, în funcție de distanța față de sursa de date, ceea ce poate influența și optimiza performanța în funcție de topologia sistemului.
  • Tipuri de paralelism:
    • La nivel de bit - operatii logice
    • La nivel de instructiune (ex. adunarea a doi vectori)
    • La nivel de task:
      • Multi-Tasking(pot comunica și procesele)
      • Multi-Threading
  • Threads vs cores
    • Procesele reprezintă instanțe de program.
    • Un proces poate avea mai multe fire de execuție (threads).
    • Fir de execuție (thread) - Flux de control secvențial în cadrul unui proces
    • Există mai multe modalități de execuție a thread-urilor: în paralel, de către sistemul de operare (time sharing), prioritate.
    • Hyperthreading - tehnologie pentru utilizarea mai eficientă a resurselor hard.
  • Primitive de sincronizare:
    • variabile atomice,
    • semafoare
      • semafoare binare (mutexe)
      • secțiuni critice
    • bariere
  • Excluderea mutuală, cunoscută și sub denumirea de mutex, este esențială pentru a preveni accesul simultan la resurse partajate.
    • Algoritmul lui Dekker:
      • Este unul dintre primele algoritmi care oferă o soluție la problema excluziunii mutuale.
      • Funcționează doar cu două fire de execuție
      • Nu există starvation (adică un proces nu va fi blocat la nesfârșit).
    • Algoritmul lui Dijkstra este o extensie a ideilor lui Dekker și oferă o soluție similară, însă este mai complex și permite implementarea pentru un număr mai mare de procese.
    • Algoritmul lui Peterson, pe de altă parte, este elegant și simplu, fiind destinat de asemenea pentru două procese, și se bazează pe utilizarea a două variabile pentru a controla accesul la secțiunile critice.

Alte notiuni

  • O situație de concurență în care rezultatul final al unui proces depinde de ordinea în care procesele concurente sunt executate. Aceasta poate duce la comportamente neprevăzute și erori, cum ar fi pierderea datelor sau rezultatele incorecte. Race condition apare adesea în sisteme de operare sau în programele multi-thread, unde mai multe fire de execuție accesează simultan resurse partajate fără o sincronizare adecvată. Prevenirea acestor condiții de cursă se poate realiza prin utilizarea de mecanisme de sincronizare, cum ar fi mutex-uri, semafoare sau blocaje. Evaluarea și designul sistemelor trebuie să includă măsuri pentru a evita apariția acestor probleme, asigurând astfel corectitudinea și stabilitatea aplicațiilor.
  • Alte primitive de procesare: lock(), unlock()

Studying That Suits You

Use AI to generate personalized quizzes and flashcards to suit your learning preferences.

Quiz Team

Related Documents

Description

Acest curs oferă o introducere în algoritmii paraleli și distribuiți, explorând conceptele fundamentale și aplicațiile acestora. Profesorul Ciprian Dobre va ghida studenții printr-un program structurat, care include teme, laboratoare și evaluări. Este esențial să participați pentru a obține nota minimă necesară în semestrul universitar.

More Like This

Parallel Systems and Algorithms Lecture 1
15 questions
Algoritmi Paraleli și Distribuiți
45 questions
Algoritmi Paraleli și Distribuiți
20 questions
Use Quizgecko on...
Browser
Browser