Ternarni operator je dva puta sporiji od bloka if-else?

Svugdje sam pročitao da bi ternarni operator trebao biti brži od, ili barem isti kao i njegov ekvivalent if - else block.

Međutim, učinio sam sljedeći test i ustanovio da to nije slučaj:

 Random r = new Random(); int[] array = new int[20000000]; for(int i = 0; i < array.Length; i++) { array[i] = r.Next(int.MinValue, int.MaxValue); } Array.Sort(array); long value = 0; DateTime begin = DateTime.UtcNow; foreach (int i in array) { if (i > 0) { value += 2; } else { value += 3; } // if-else block above takes on average 85 ms // OR I can use a ternary operator: // value += i > 0 ? 2 : 3; // takes 157 ms } DateTime end = DateTime.UtcNow; MessageBox.Show("Measured time: " + (end-begin).TotalMilliseconds + " ms.\r\nResult = " + value.ToString()); 

Za pokretanje gore navedenog koda potrebno je 85 ms. Ali ako komentiram isječak if - else i odkomentiram trostruki redak, bit će potrebno oko 157 ms.

Zašto se to događa?

238
26 июня '13 в 22:18 2013-06-26 22:18 user1032613 postavljen je 26. lipnja 2013. u 22:18 sati 2013-06-26 22:18
@ 9 odgovora

Da bismo odgovorili na ovo pitanje, razmotrit ćemo šifru izrade koju je izradio JIT X86 i X64 za svaki od ovih slučajeva.

X86 ako / tada

  32: foreach (int i in array) 0000007c 33 D2 xor edx,edx 0000007e 83 7E 04 00 cmp dword ptr [esi+4],0 00000082 7E 1C jle 000000A0 00000084 8B 44 96 08 mov eax,dword ptr [esi+edx*4+8] 33: { 34: if (i > 0) 00000088 85 C0 test eax,eax 0000008a 7E 08 jle 00000094 35: { 36: value += 2; 0000008c 83 C3 02 add ebx,2 0000008f 83 D7 00 adc edi,0 00000092 EB 06 jmp 0000009A 37: } 38: else 39: { 40: value += 3; 00000094 83 C3 03 add ebx,3 00000097 83 D7 00 adc edi,0 0000009a 42 inc edx 32: foreach (int i in array) 0000009b 39 56 04 cmp dword ptr [esi+4],edx 0000009e 7F E4 jg 00000084 30: for (int x = 0; x < iterations; x++) 000000a0 41 inc ecx 000000a1 3B 4D F0 cmp ecx,dword ptr [ebp-10h] 000000a4 7C D6 jl 0000007C 

X86, ternarni

  59: foreach (int i in array) 00000075 33 F6 xor esi,esi 00000077 83 7F 04 00 cmp dword ptr [edi+4],0 0000007b 7E 2D jle 000000AA 0000007d 8B 44 B7 08 mov eax,dword ptr [edi+esi*4+8] 60: { 61: value += i > 0 ? 2 : 3; 00000081 85 C0 test eax,eax 00000083 7F 07 jg 0000008C 00000085 BA 03 00 00 00 mov edx,3 0000008a EB 05 jmp 00000091 0000008c BA 02 00 00 00 mov edx,2 00000091 8B C3 mov eax,ebx 00000093 8B 4D EC mov ecx,dword ptr [ebp-14h] 00000096 8B DA mov ebx,edx 00000098 C1 FB 1F sar ebx,1Fh 0000009b 03 C2 add eax,edx 0000009d 13 CB adc ecx,ebx 0000009f 89 4D EC mov dword ptr [ebp-14h],ecx 000000a2 8B D8 mov ebx,eax 000000a4 46 inc esi 59: foreach (int i in array) 000000a5 39 77 04 cmp dword ptr [edi+4],esi 000000a8 7F D3 jg 0000007D 57: for (int x = 0; x < iterations; x++) 000000aa FF 45 E4 inc dword ptr [ebp-1Ch] 000000ad 8B 45 E4 mov eax,dword ptr [ebp-1Ch] 000000b0 3B 45 F0 cmp eax,dword ptr [ebp-10h] 000000b3 7C C0 jl 00000075 

X64 ako / tada

  32: foreach (int i in array) 00000059 4C 8B 4F 08 mov r9,qword ptr [rdi+8] 0000005d 0F 1F 00 nop dword ptr [rax] 00000060 45 85 C9 test r9d,r9d 00000063 7E 2B jle 0000000000000090 00000065 33 D2 xor edx,edx 00000067 45 33 C0 xor r8d,r8d 0000006a 4C 8B 57 08 mov r10,qword ptr [rdi+8] 0000006e 66 90 xchg ax,ax 00000070 42 8B 44 07 10 mov eax,dword ptr [rdi+r8+10h] 33: { 34: if (i > 0) 00000075 85 C0 test eax,eax 00000077 7E 07 jle 0000000000000080 35: { 36: value += 2; 00000079 48 83 C5 02 add rbp,2 0000007d EB 05 jmp 0000000000000084 0000007f 90 nop 37: } 38: else 39: { 40: value += 3; 00000080 48 83 C5 03 add rbp,3 00000084 FF C2 inc edx 00000086 49 83 C0 04 add r8,4 32: foreach (int i in array) 0000008a 41 3B D2 cmp edx,r10d 0000008d 7C E1 jl 0000000000000070 0000008f 90 nop 30: for (int x = 0; x < iterations; x++) 00000090 FF C1 inc ecx 00000092 41 3B CC cmp ecx,r12d 00000095 7C C9 jl 0000000000000060 

X64, trostruko

  59: foreach (int i in array) 00000044 4C 8B 4F 08 mov r9,qword ptr [rdi+8] 00000048 45 85 C9 test r9d,r9d 0000004b 7E 2F jle 000000000000007C 0000004d 45 33 C0 xor r8d,r8d 00000050 33 D2 xor edx,edx 00000052 4C 8B 57 08 mov r10,qword ptr [rdi+8] 00000056 8B 44 17 10 mov eax,dword ptr [rdi+rdx+10h] 60: { 61: value += i > 0 ? 2 : 3; 0000005a 85 C0 test eax,eax 0000005c 7F 07 jg 0000000000000065 0000005e B8 03 00 00 00 mov eax,3 00000063 EB 05 jmp 000000000000006A 00000065 B8 02 00 00 00 mov eax,2 0000006a 48 63 C0 movsxd rax,eax 0000006d 4C 03 E0 add r12,rax 00000070 41 FF C0 inc r8d 00000073 48 83 C2 04 add rdx,4 59: foreach (int i in array) 00000077 45 3B C2 cmp r8d,r10d 0000007a 7C DA jl 0000000000000056 57: for (int x = 0; x < iterations; x++) 0000007c FF C1 inc ecx 0000007e 3B CD cmp ecx,ebp 00000080 7C C6 jl 0000000000000048 

Prvo: zašto je X86 kod puno sporiji od X64?

To je zbog sljedećih karakteristika koda:

  • X64 ima nekoliko dodatnih registara, a svaki registar ima 64-bitne vrijednosti. To omogućuje X64 JIT-u da u potpunosti izvrši unutarnju petlju koristeći registre osim učitavanja i iz niza, dok X86 JIT stavlja u petlju nekoliko operacija stog (pristup memoriji).
  • value je 64-bitni cijeli broj koji zahtijeva 2 strojne upute za X86 ( add , nakon čega slijedi adc ), ali samo 1 za X64 ( add ).

Drugo: zašto ternarni operater radi sporije na X86 i X64?

To je zbog male razlike u redoslijedu operacija koje utječu na JIT optimizator. Za JIT za ternarnog operatera, umjesto direktnog kodiranja 2 i 3 u strojnim uputama sami add JIT stvara intermedijernu varijablu (u registru) za pohranjivanje rezultata. Tada se ovaj registar proširuje na 32 bita do 64 bita prije nego što se doda value . Budući da se sve to radi u registrima za X64, unatoč značajnom povećanju složenosti ternarnog operatora, neto utjecaj je donekle minimiziran.

S druge strane, X86 JIT utječe u većoj mjeri, budući da dodavanje nove srednje vrijednosti u unutarnju petlju uzrokuje da "prospe" drugu vrijednost, što rezultira barem dva dodatna pristupa memoriji u internoj petlji (vidi reference na [ebp-14h] u trostrukom kodu X86).

370
27 июня '13 в 1:01 2013-06-27 01:01 odgovor je dao Sam Harwell 27. lipnja '13. u 1:01 2013-06-27 01:01

EDIT: sve promjene ... pogledajte dolje.

Ne mogu reproducirati vaše rezultate na x64 CLR, ali mogu na x86. Na x64, vidim malu razliku (manje od 10%) između uvjetnog operatora i if / else, ali je mnogo manji od onoga što vidite.

Izvršio sam sljedeće izmjene:

  • Pokreni u konzolnoj aplikaciji
  • Stvorite s /o+ /debug- i izvan programa za ispravljanje pogrešaka
  • Pokrenite oba isječaka koda jednom na JIT, zatim mnogo puta za bolju točnost.
  • Koristite Stopwatch

Rezultati od /platform:x64 (bez "zanemarivanja"):

 if/else with 1 iterations: 17ms conditional with 1 iterations: 19ms if/else with 1000 iterations: 17875ms conditional with 1000 iterations: 19089ms 

Rezultati od /platform:x86 (bez "zanemarivanja"):

border=0
 if/else with 1 iterations: 18ms conditional with 1 iterations: 49ms if/else with 1000 iterations: 17901ms conditional with 1000 iterations: 47710ms 

Pojedinosti o mom sustavu:

  • x64 i7-2720QM CPU @ 2.20 GHz
  • 64-bitna verzija sustava Windows 8
  • .NET 4.5

Za razliku od prethodnih, mislim da vidite pravu razliku - i sve je to povezano s x86 JIT-om. Ne bih htio točno reći što uzrokuje razliku - poruku kasnije mogu ažurirati detaljnijim informacijama, ako mogu naporno raditi kako bih išao na cordbg :)

Zanimljivo je da bez prethodnog razvrstavanja polja završim testovima koji traju oko 4.5x, barem na x64. Mislim da je ovo povezano s predviđanjem grane.

šifra:

 using System; using System.Diagnostics; class Test { static void Main() { Random r = new Random(0); int[] array = new int[20000000]; for(int i = 0; i < array.Length; i++) { array[i] = r.Next(int.MinValue, int.MaxValue); } Array.Sort(array); // JIT everything... RunIfElse(array, 1); RunConditional(array, 1); // Now really time it RunIfElse(array, 1000); RunConditional(array, 1000); } static void RunIfElse(int[] array, int iterations) { long value = 0; Stopwatch sw = Stopwatch.StartNew(); for (int x = 0; x < iterations; x++) { foreach (int i in array) { if (i > 0) { value += 2; } else { value += 3; } } } sw.Stop(); Console.WriteLine("if/else with {0} iterations: {1}ms", iterations, sw.ElapsedMilliseconds); // Just to avoid optimizing everything away Console.WriteLine("Value (ignore): {0}", value); } static void RunConditional(int[] array, int iterations) { long value = 0; Stopwatch sw = Stopwatch.StartNew(); for (int x = 0; x < iterations; x++) { foreach (int i in array) { value += i > 0 ? 2 : 3; } } sw.Stop(); Console.WriteLine("conditional with {0} iterations: {1}ms", iterations, sw.ElapsedMilliseconds); // Just to avoid optimizing everything away Console.WriteLine("Value (ignore): {0}", value); } } 
62
26 июня '13 в 22:30 2013-06-26 22:30 Odgovor je dao Jon Skeet 26. lipnja '13. U 22:30. 2013-06-26 22:30

Razlika zapravo nema mnogo veze s if / else vs trernary.

Gledajući zadirkivačko rastavljanje (ovdje neću vratiti, vidi odgovor @ 280Z28), ispada da uspoređujete jabuke i naranče . U jednom slučaju, kreirate dvije različite += operacije s konstantnim vrijednostima, a koju odaberete ovisi o stanju, au drugom slučaju kreirate += , gdje vrijednost za dodavanje ovisi o stanju.

Ako doista želite usporediti if / else vs trernary, to će biti pravednija usporedba (sada će oba biti jednako "spora", ili možemo čak reći da je trostruki bit brži):

 int diff; if (i > 0) diff = 2; else diff = 3; value += diff; 

protiv.

 value += i > 0 ? 2 : 3; 

Sada rastavljanje za if/else postaje kako je prikazano dolje. Napominjemo da je to nešto lošije od ternarnog slučaja, jer je prestalo koristiti registre za petlju varijablu ( i ).

  if (i > 0) 0000009d cmp dword ptr [ebp-20h],0 000000a1 jle 000000AD { diff = 2; 000000a3 mov dword ptr [ebp-24h],2 000000aa nop 000000ab jmp 000000B4 } else { diff = 3; 000000ad mov dword ptr [ebp-24h],3 } value += diff; 000000b4 mov eax,dword ptr [ebp-18h] 000000b7 mov edx,dword ptr [ebp-14h] 000000ba mov ecx,dword ptr [ebp-24h] 000000bd mov ebx,ecx 000000bf sar ebx,1Fh 000000c2 add eax,ecx 000000c4 adc edx,ebx 000000c6 mov dword ptr [ebp-18h],eax 000000c9 mov dword ptr [ebp-14h],edx 000000cc inc dword ptr [ebp-28h] 
42
27 июня '13 в 11:25 2013-06-27 11:25 odgovor je dao Eren Ersönmez 27. lipnja '13. u 11:25 2013-06-27 11:25

promijeniti:

Dodan je primjer koji se može izvršiti pomoću naredbe if-else, ali ne i uvjetne naredbe.


Prije odgovora pogledajte [ što je brže? u blogu gospodina Lipperta. I mislim da gospodin Odgovor Ersönmeza ovdje je najispravniji.

Pokušavam spomenuti nešto što moramo imati na umu s programskim jezikom visoke razine.

Prvo, nikada nisam čuo da bi uvjetni operator trebao biti brži ili jednak u izvedbi s if-else operatorom u C♯ .

Razlog je jednostavan, ako nema operacije s izjavom if-else:

 if (i > 0) { value += 2; } else { } 

Zahtjev uvjetne izjave mora biti na bilo kojoj strani, au C♯, obje strane : također potrebne : one moraju biti istog tipa. To se jednostavno razlikuje od izjave if-else. Dakle, vaše pitanje postaje pitanje kako se generira instrukcija strojnog koda, tako da postoji razlika u izvedbi.

S uvjetnim operatorom semantički je:

Bez obzira na to koji se izraz izražava, postoji vrijednost.

Ali s instrukcijom if-else:

Ako se izraz izražava kao istinit, učinite nešto; ako ne, učinite još nešto.

Vrijednost nije nužno povezana s if-else izrazom. Vaša pretpostavka je moguća samo uz optimizaciju.

Drugi primjer koji pokazuje razliku između njih izgledat će ovako:

 var array1=new[] { 1, 2, 3 }; var array2=new[] { 5, 6, 7 }; if(i>0) array1[0]=4; else array2[0]=4; 

Gornji kod kompilacije, međutim, zamjena izraza if-else s uvjetnom naredbom jednostavno se ne kompajlira:

 var array1=new[] { 1, 2, 3 }; var array2=new[] { 5, 6, 7 }; (i>0?array1[0]:array2[0])=4; // incorrect usage 

Uvjetna naredba i naredbe if-else su konceptualno iste, ako učinite isto, možda čak i brže s uvjetnom naredbom u C , budući da je C bliže platformi za montažu.


Za izvorni kôd koji ste dali, uvjetna naredba se koristi u foreach petlji, što može uništiti situaciju da bi se vidjela razlika između njih. Stoga predlažem sljedeći kôd:

 public static class TestClass { public static void TestConditionalOperator(int i) { long value=0; value+=i>0?2:3; } public static void TestIfElse(int i) { long value=0; if(i>0) { value+=2; } else { value+=3; } } public static void TestMethod() { TestConditionalOperator(0); TestIfElse(0); } } 

i sljedeće dvije verzije IL su optimizirane, a ne. Budući da su duge, koristim sliku za prikaz, a desna strana je optimizirana:

(Kliknite da biste vidjeli sliku u punoj veličini.) 2019

Učinio sam ono što je John Skeet učinio, i proveo 1 iteraciju i 1000 iteracija i dobio drugačiji rezultat od OP-a i Jona. U mom, trostruko brže. U nastavku je točan kôd:

 static void runIfElse(int[] array, int iterations) { long value = 0; Stopwatch ifElse = new Stopwatch(); ifElse.Start(); for (int c = 0; c < iterations; c++) { foreach (int i in array) { if (i > 0) { value += 2; } else { value += 3; } } } ifElse.Stop(); Console.WriteLine(String.Format("Elapsed time for If-Else: {0}", ifElse.Elapsed)); } static void runTernary(int[] array, int iterations) { long value = 0; Stopwatch ternary = new Stopwatch(); ternary.Start(); for (int c = 0; c < iterations; c++) { foreach (int i in array) { value += i > 0 ? 2 : 3; } } ternary.Stop(); Console.WriteLine(String.Format("Elapsed time for Ternary: {0}", ternary.Elapsed)); } static void Main(string[] args) { Random r = new Random(); int[] array = new int[20000000]; for (int i = 0; i < array.Length; i++) { array[i] = r.Next(int.MinValue, int.MaxValue); } Array.Sort(array); long value = 0; runIfElse(array, 1); runTernary(array, 1); runIfElse(array, 1000); runTernary(array, 1000); Console.ReadLine(); } 

Rezultat mog programa:

Proteklo vrijeme za If-Else: 00: 00: 00.0140543

Proteklo vrijeme za Ternara: 00: 00: 00.0136723

Proteklo vrijeme za If-Else: 00: 00: 14.0167870

Proteklo vrijeme za Ternara: 00: 00: 13.9418520

Još jedna vožnja u milisekundama:

Proteklo vrijeme za If-Else: 20

Proteklo vrijeme za Ternar: 19

Proteklo vrijeme za If-Else: 13854

Proteklo vrijeme za Ternar: 13610

Ovo radi u 64-bitnoj verziji XP-a, a ja sam trčao bez ispravljanja.

Uredi - pokreni u x86:

Postoji velika razlika u korištenju x86. To je učinjeno bez otklanjanja pogrešaka i na istom x64 xp računalu kao i prije, ali izgrađeno za x86 procesore. Ovo je više kao OP.

Proteklo vrijeme za If-Else: 18

Proteklo vrijeme za Ternar: 35

Proteklo vrijeme za If-Else: 20512

Proteklo vrijeme za Ternar: 32673

7
26 июня '13 в 23:16 2013-06-26 23:16 odgovor je dao Shaz 26. lipnja '13. u 11:16 sati 2013-06-26 23:16

Generirani asemblerski kod će ispričati priču:

 a = (b > c) ? 1 : 0; 

stvara:

 mov edx, DWORD PTR a[rip] mov eax, DWORD PTR b[rip] cmp edx, eax setg al 

budući da:

 if (a > b) printf("a"); else printf("b"); 

stvara:

 mov edx, DWORD PTR a[rip] mov eax, DWORD PTR b[rip] cmp edx, eax jle .L4 ;printf a jmp .L5 .L4: ;printf b .L5: 

Dakle, trostruko može biti kraće i brže jednostavno zbog korištenja manje instrukcija i bez prijelaza ako tražite istinito / netočno. Ako koristite vrijednosti koje nisu 1 i 0, dobit ćete isti kôd kao / else, na primjer:

 a = (b > c) ? 2 : 3; 

stvara:

 mov edx, DWORD PTR b[rip] mov eax, DWORD PTR c[rip] cmp edx, eax jle .L6 mov eax, 2 jmp .L7 .L6: mov eax, 3 .L7: 

To je isto kao ako / drugo.

5
27 июня '13 в 0:15 2013-06-27 00:15 odgovor je dao user2513931 27. lipnja '13 u 0:15 2013-06-27 00:15

Gledajući generirani IL, postoji manje od 16 operacija u njemu nego u instrukciji if / else (kopiranje i lijepljenje @JonSkeet koda). Međutim, to ne znači da bi trebao biti brži proces!

Da sumiramo razlike u IL, metoda if / else se prevodi na isti način kao i kod C # koji čita (radi dodavanje u grani), dok uvjetni kod učitava 2 ili 3 na stog (ovisno o vrijednosti) i zatim dodaje svoje vrijednosti izvan uvjetnog izraza.

Druga razlika je u upotrebi instrukcije grana. Metoda if / else koristi brtrue (ako je grana istinita) da ide po prvom uvjetu, a bezuvjetna granica od prvog if izraza. Uvjetni kod koristi bgt (grana ako je više od) umjesto brtrue, što može biti sporiji.

Također (samo čitanje o grani predviđanja) postoji svibanj biti izvršenje kaznu za manje grane. Uslovna grana ima samo jednu naredbu unutar grane, ali ako / else ima 7. To također objašnjava zašto postoji razlika između korištenja dugog i int, jer promjena u int smanjuje broj uputa u if / else granama za 1 (što čini manje manje)

4
27 июня '13 в 0:24 2013-06-27 00:24 odgovor je dao Matthew Steeples 27. lipnja '13. u 0:24 2013-06-27 00:24

Trčanje bez ispravljanja ctrl + F5 čini se da debugger usporava oboje, ali čini se da usporava rad ternarnog operatora.

Kada pokrenem sljedeći kôd, evo mojih rezultata. Mislim da je mala razlika u milisekundama uzrokovana kompajler optimizacije max = max i uklanjanje, ali vjerojatno ne čine ovu optimizaciju za ternary operatora. Ako netko može provjeriti građu i potvrditi je, bit će strašan.

 --Run #1-- Type | Milliseconds Ternary 706 If 704 %: .9972 --Run #2-- Type | Milliseconds Ternary 707 If 704 %: .9958 --Run #3-- Type | Milliseconds Ternary 706 If 704 %: .9972 

kod

  for (int t = 1; t != 10; t++) { var s = new System.Diagnostics.Stopwatch(); var r = new Random(123456789); //r int[] randomSet = new int[1000]; //a for (int i = 0; i < 1000; i++) //n randomSet[i] = r.Next(); //dom long _ternary = 0; //store long _if = 0; //time int max = 0; //result s.Start(); for (int q = 0; q < 1000000; q++) { for (int i = 0; i < 1000; i++) max = max > randomSet[i] ? max : randomSet[i]; } s.Stop(); _ternary = s.ElapsedMilliseconds; max = 0; s = new System.Diagnostics.Stopwatch(); s.Start(); for (int q = 0; q < 1000000; q++) { for (int i = 0; i < 1000; i++) if (max > randomSet[i]) max = max; // I think the compiler may remove this but not for the ternary causing the speed difference. else max = randomSet[i]; } s.Stop(); _if = s.ElapsedMilliseconds; Console.WriteLine("--Run #" + t+"--"); Console.WriteLine("Type | Milliseconds\nTernary {0}\nIf {1}\n%: {2}", _ternary, _if,((decimal)_if/(decimal)_ternary).ToString("#.####")); } 
4
26 июня '13 в 22:57 2013-06-26 22:57 Odgovor daje CodeCamper 26. lipnja '13 u 10:57 2013-06-14 22:57

U sljedećem kodu, ako / else čini se da je oko 1,4 puta brži od ternarnog operatora. Međutim, otkrio sam da uvođenje privremene varijable smanjuje vrijeme rada ternarnog operatora za oko 1,4 puta:

If / Else: 98 ms

Vrijeme: 141 ms

Ternar s temp var: 100 ms

 using System; using System.Diagnostics; namespace ConsoleApplicationTestIfElseVsTernaryOperator { class Program { static void Main(string[] args) { Random r = new Random(0); int[] array = new int[20000000]; for (int i = 0; i < array.Length; i++) { array[i] = r.Next(int.MinValue, int.MaxValue); } Array.Sort(array); long value; Stopwatch stopwatch = new Stopwatch(); value = 0; stopwatch.Restart(); foreach (int i in array) { if (i > 0) { value += 2; } else { value += 3; } // 98 ms } stopwatch.Stop(); Console.WriteLine("If/Else: " + stopwatch.ElapsedMilliseconds.ToString() + " ms"); value = 0; stopwatch.Restart(); foreach (int i in array) { value += (i > 0) ? 2 : 3; // 141 ms } stopwatch.Stop(); Console.WriteLine("Ternary: " + stopwatch.ElapsedMilliseconds.ToString() + " ms"); value = 0; int tempVar = 0; stopwatch.Restart(); foreach (int i in array) { tempVar = (i > 0) ? 2 : 3; value += tempVar; // 100ms } stopwatch.Stop(); Console.WriteLine("Ternary with temp var: " + stopwatch.ElapsedMilliseconds.ToString() + " ms"); Console.ReadKey(true); } } } 
1
14 окт. Odgovor dao je Aleksej Novikov 14. listopada. 2015-10-14 15:21 '15 u 15:21 2015-10-14 15:21

Ostala pitanja o oznaka ili Postavi pitanje