1 |
C $Header: /u/gcmpack/models/MITgcmUV/eesupp/src/timers.F,v 1.1 1998/06/10 21:38:28 adcroft Exp $ |
2 |
|
3 |
#include "CPP_EEOPTIONS.h" |
4 |
|
5 |
C-- File utils.F: General purpose support routines |
6 |
C-- Contents |
7 |
C-- TIMER_INDEX - Returns index associated with timer name. |
8 |
C-- M TIMER_CONTROL - Implements timer functions for given machine. |
9 |
C-- TIMER_PRINT - Print CPU timer statitics. |
10 |
C-- TIMER_PRINTALL - Prints all CPU timers statistics. |
11 |
C-- TIMER_START - Starts CPU timer for code section. |
12 |
C-- TIMER_STOP - Stop CPU tier for code section. |
13 |
C-- Routines marked "M" contain specific machine dependent code. |
14 |
C-- Routines marked "U" contain UNIX OS calls. |
15 |
|
16 |
CStartOfInterface |
17 |
INTEGER FUNCTION TIMER_INDEX ( |
18 |
I name,timerNames,maxTimers,nTimers ) |
19 |
C /==========================================================\ |
20 |
C | FUNCTION TIMER_INDEX | |
21 |
C | o Timing support routine. | |
22 |
C |==========================================================| |
23 |
C | Return index in timer data structure of timer named | |
24 |
C | by the function argument "name". | |
25 |
C \==========================================================/ |
26 |
INTEGER maxTimers |
27 |
INTEGER nTimers |
28 |
CHARACTER*(*) name |
29 |
CHARACTER*(*) timerNames(maxTimers) |
30 |
CEndOfInterface |
31 |
INTEGER I |
32 |
C |
33 |
TIMER_INDEX = 0 |
34 |
IF ( name .EQ. ' ' ) THEN |
35 |
TIMER_INDEX = -1 |
36 |
ELSE |
37 |
DO 10 I = 1, nTimers |
38 |
IF ( name .NE. timerNames(I) ) GOTO 10 |
39 |
TIMER_INDEX = I |
40 |
GOTO 11 |
41 |
10 CONTINUE |
42 |
11 CONTINUE |
43 |
ENDIF |
44 |
RETURN |
45 |
END |
46 |
|
47 |
CStartOfInterface |
48 |
SUBROUTINE TIMER_CONTROL ( name , action , callProc , myThreadId ) |
49 |
C /==========================================================\ |
50 |
C | SUBROUTINE TIMER_CONTROL | |
51 |
C | o Timing routine. | |
52 |
C |==========================================================| |
53 |
C | User callable interface to timing routines. Timers are | |
54 |
C | created, stopped, started and queried only through this | |
55 |
C | rtouine. | |
56 |
C \==========================================================/ |
57 |
#include "SIZE.h" |
58 |
#include "EEPARAMS.h" |
59 |
#include "EESUPPORT.h" |
60 |
CHARACTER*(*) name |
61 |
CHARACTER*(*) action |
62 |
CHARACTER*(*) callProc |
63 |
INTEGER myThreadId |
64 |
CEndOfInterface |
65 |
C |
66 |
INTEGER TIMER_INDEX |
67 |
INTEGER IFNBLNK |
68 |
INTEGER ILNBLNK |
69 |
EXTERNAL TIMER_INDEX |
70 |
EXTERNAL IFNBLNK |
71 |
EXTERNAL ILNBLNK |
72 |
C |
73 |
INTEGER maxTimers |
74 |
INTEGER maxString |
75 |
PARAMETER ( maxTimers = 40 ) |
76 |
PARAMETER ( maxString = 80 ) |
77 |
C |
78 |
INTEGER timerStarts( maxTimers , MAX_NO_THREADS) |
79 |
SAVE timerStarts |
80 |
INTEGER timerStops ( maxTimers , MAX_NO_THREADS) |
81 |
SAVE timerStops |
82 |
Real*8 timerUser ( maxTimers , MAX_NO_THREADS) |
83 |
SAVE timerUser |
84 |
Real*8 timerWall ( maxTimers , MAX_NO_THREADS) |
85 |
SAVE timerWall |
86 |
Real*8 timerSys ( maxTimers , MAX_NO_THREADS) |
87 |
SAVE timerSys |
88 |
Real*8 timerT0User( maxTimers , MAX_NO_THREADS) |
89 |
SAVE timerT0User |
90 |
Real*8 timerT0Wall( maxTimers , MAX_NO_THREADS) |
91 |
SAVE timerT0Wall |
92 |
Real*8 timerT0Sys ( maxTimers , MAX_NO_THREADS) |
93 |
SAVE timerT0Sys |
94 |
C =============================================================== |
95 |
C |
96 |
INTEGER timerStatus( maxTimers , MAX_NO_THREADS) |
97 |
SAVE timerStatus |
98 |
INTEGER timerNameLen( maxTimers , MAX_NO_THREADS) |
99 |
SAVE timerNameLen |
100 |
CHARACTER*(maxString) timerNames( maxTimers , MAX_NO_THREADS) |
101 |
SAVE timerNames |
102 |
CHARACTER*(maxString) timerAction |
103 |
INTEGER nTimers(MAX_NO_THREADS) |
104 |
CHARACTER*(maxString) tmpName |
105 |
CHARACTER*(maxString) tmpAction |
106 |
INTEGER iTimer |
107 |
INTEGER ISTART |
108 |
INTEGER IEND |
109 |
INTEGER STOPPED |
110 |
PARAMETER ( STOPPED = 0 ) |
111 |
INTEGER RUNNING |
112 |
PARAMETER ( RUNNING = 1 ) |
113 |
CHARACTER*(*) STOP |
114 |
PARAMETER ( STOP = 'STOP' ) |
115 |
CHARACTER*(*) START |
116 |
PARAMETER ( START = 'START' ) |
117 |
CHARACTER*(*) PRINT |
118 |
PARAMETER ( PRINT = 'PRINT' ) |
119 |
CHARACTER*(*) PRINTALL |
120 |
PARAMETER ( PRINTALL = 'PRINTALL' ) |
121 |
INTEGER I |
122 |
Real*8 userTime |
123 |
Real*8 systemTime |
124 |
Real*8 wallClockTime |
125 |
CHARACTER*(MAX_LEN_MBUF) msgBuffer |
126 |
C |
127 |
DATA nTimers /MAX_NO_THREADS*0/ |
128 |
SAVE nTimers |
129 |
C |
130 |
ISTART = IFNBLNK(name) |
131 |
IEND = ILNBLNK(name) |
132 |
IF ( IEND - ISTART + 1 .GT. maxString ) GOTO 901 |
133 |
IF ( ISTART .NE. 0 ) THEN |
134 |
tmpName = name(ISTART:IEND) |
135 |
CALL UCASE( tmpName ) |
136 |
ELSE |
137 |
tmpName = ' ' |
138 |
ENDIF |
139 |
ISTART = IFNBLNK(action) |
140 |
IEND = ILNBLNK(action) |
141 |
IF ( ISTART .EQ. 0 ) GOTO 902 |
142 |
IF ( IEND - ISTART + 1 .GT. maxString ) GOTO 903 |
143 |
tmpAction = action(ISTART:IEND) |
144 |
CALL UCASE( tmpAction ) |
145 |
C |
146 |
iTimer=TIMER_INDEX(tmpName,timerNames(1,myThreadId),maxTimers,nTimers(myThreadId)) |
147 |
C |
148 |
IF ( tmpAction .EQ. START ) THEN |
149 |
IF ( iTimer .EQ. 0 ) THEN |
150 |
IF ( nTimers(myThreadId) .EQ. maxTimers ) GOTO 904 |
151 |
nTimers(myThreadId) = nTimers(myThreadId) + 1 |
152 |
iTimer = nTimers(myThreadId) |
153 |
timerNames(iTimer,myThreadId) = tmpName |
154 |
timerNameLen(iTimer,myThreadId) = ILNBLNK(tmpName)-IFNBLNK(tmpName)+1 |
155 |
timerUser(iTimer,myThreadId) = 0. |
156 |
timerSys (iTimer,myThreadId) = 0. |
157 |
timerWall(iTimer,myThreadId) = 0. |
158 |
timerStarts(iTimer,myThreadId) = 0 |
159 |
timerStops (iTimer,myThreadId) = 0 |
160 |
timerStatus(iTimer,myThreadId) = STOPPED |
161 |
ENDIF |
162 |
IF ( timerStatus(iTimer,myThreadId) .NE. RUNNING ) THEN |
163 |
CALL TIMER_GET_TIME( userTime, systemTime, wallClockTime ) |
164 |
timerT0User(iTimer,myThreadId) = userTime |
165 |
timerT0Sys(iTimer,myThreadId) = systemTime |
166 |
timerT0Wall(iTimer,myThreadId) = wallClockTime |
167 |
timerStatus(iTimer,myThreadId) = RUNNING |
168 |
timerStarts(iTimer,myThreadId) = timerStarts(iTimer,myThreadId)+1 |
169 |
ENDIF |
170 |
ELSEIF ( tmpAction .EQ. STOP ) THEN |
171 |
IF ( iTimer .EQ. 0 ) GOTO 905 |
172 |
IF ( timerStatus(iTimer,myThreadId) .EQ. RUNNING ) THEN |
173 |
CALL TIMER_GET_TIME( userTime, systemTime, wallClockTime ) |
174 |
timerUser(iTimer,myThreadId) = timerUser(iTimer,myThreadId) + |
175 |
& userTime - |
176 |
& timerT0User(iTimer,myThreadId) |
177 |
timerSys (iTimer,myThreadId) = timerSys(iTimer,myThreadId) + |
178 |
& systemTime - |
179 |
& timerT0Sys(iTimer,myThreadId) |
180 |
timerWall(iTimer,myThreadId) = timerWall(iTimer,myThreadId) + |
181 |
& wallClockTime - |
182 |
& timerT0Wall(iTimer,myThreadId) |
183 |
timerStatus(iTimer,myThreadId) = STOPPED |
184 |
timerStops (iTimer,myThreadId) = timerStops (iTimer,myThreadId)+1 |
185 |
ENDIF |
186 |
ELSEIF ( tmpAction .EQ. PRINT ) THEN |
187 |
IF ( iTimer .EQ. 0 ) GOTO 905 |
188 |
WRITE(msgBuffer,*) |
189 |
& ' Seconds in section "', |
190 |
& timerNames(iTimer,myThreadId)(1:timerNameLen(iTimer,myThreadId)),'":' |
191 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
192 |
WRITE(msgBuffer,*) ' User time:',timerUser(iTimer,myThreadId) |
193 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
194 |
WRITE(msgBuffer,*) ' System time:',timerSys(iTimer,myThreadId) |
195 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
196 |
WRITE(msgBuffer,*) ' Wall clock time:',timerWall(iTimer,myThreadId) |
197 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
198 |
WRITE(msgBuffer,*) ' No. starts:',timerStarts(iTimer,myThreadId) |
199 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
200 |
WRITE(msgBuffer,*) ' No. stops:',timerStops(iTimer,myThreadId) |
201 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
202 |
ELSEIF ( tmpAction .EQ. PRINTALL ) THEN |
203 |
DO 10 I = 1, nTimers(myThreadId) |
204 |
WRITE(msgBuffer,*) ' Seconds in section "', |
205 |
& timerNames(I,myThreadId)(1:timerNameLen(I,myThreadId)),'":' |
206 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
207 |
WRITE(msgBuffer,*) ' User time:',timerUser(I,myThreadId) |
208 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
209 |
WRITE(msgBuffer,*) ' System time:',timerSys(I,myThreadId) |
210 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
211 |
WRITE(msgBuffer,*) ' Wall clock time:',timerWall(I,myThreadId) |
212 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
213 |
WRITE(msgBuffer,*) ' No. starts:',timerStarts(I,myThreadId) |
214 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
215 |
WRITE(msgBuffer,*) ' No. stops:',timerStops(I,myThreadId) |
216 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
217 |
10 CONTINUE |
218 |
ELSE |
219 |
GOTO 903 |
220 |
ENDIF |
221 |
C |
222 |
1000 CONTINUE |
223 |
C |
224 |
RETURN |
225 |
901 CONTINUE |
226 |
WRITE(msgBuffer,'(A)') |
227 |
&' ' |
228 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
229 |
WRITE(msgBuffer,*) |
230 |
&'*** WARNING WARNING WARNING WARNING WARNING WARNING ***' |
231 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
232 |
WRITE(msgBuffer,*) |
233 |
&'procedure: "',callProc,'".' |
234 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
235 |
WRITE(msgBuffer,*) |
236 |
&'Timer name "',name(ISTART:IEND),'" is invalid.' |
237 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
238 |
WRITE(msgBuffer,*) |
239 |
&' Names must have fewer than',maxString+1,' characters.' |
240 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
241 |
WRITE(msgBuffer,*) |
242 |
&'*******************************************************' |
243 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
244 |
GOTO 1000 |
245 |
902 CONTINUE |
246 |
WRITE(msgBuffer,*) |
247 |
&' ' |
248 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
249 |
WRITE(msgBuffer,*) |
250 |
&'*** WARNING WARNING WARNING WARNING WARNING WARNING ***' |
251 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
252 |
WRITE(msgBuffer,*) |
253 |
&'procedure: "',callProc,'".' |
254 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
255 |
WRITE(msgBuffer,*) |
256 |
&' No timer action specified.' |
257 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
258 |
WRITE(msgBuffer,*) |
259 |
&' Valid actions are:' |
260 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
261 |
WRITE(msgBuffer,*) |
262 |
&' "START", "STOP", "PRINT" and "PRINTALL".' |
263 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
264 |
WRITE(msgBuffer,*) |
265 |
&'*******************************************************' |
266 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
267 |
GOTO 1000 |
268 |
903 CONTINUE |
269 |
WRITE(msgBuffer,*) |
270 |
&' ' |
271 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
272 |
WRITE(msgBuffer,*) |
273 |
&'*** WARNING WARNING WARNING WARNING WARNING WARNING ***' |
274 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
275 |
WRITE(msgBuffer,*) |
276 |
&'procedure: "',callProc,'".' |
277 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
278 |
WRITE(msgBuffer,*) |
279 |
&'Timer action"',name(ISTART:IEND),'" is invalid.' |
280 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
281 |
WRITE(msgBuffer,*) |
282 |
&' Valid actions are:' |
283 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
284 |
WRITE(msgBuffer,*) |
285 |
&' "START", "STOP", "PRINT" and "PRINTALL".' |
286 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
287 |
WRITE(msgBuffer,*) |
288 |
&'*******************************************************' |
289 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
290 |
GOTO 1000 |
291 |
904 CONTINUE |
292 |
WRITE(msgBuffer,*) |
293 |
&' ' |
294 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
295 |
WRITE(msgBuffer,*) |
296 |
&'*** WARNING WARNING WARNING WARNING WARNING WARNING ***' |
297 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
298 |
WRITE(msgBuffer,*) |
299 |
&'procedure: "',callProc,'".' |
300 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
301 |
WRITE(msgBuffer,*) |
302 |
&'Timer "',name(ISTART:IEND),'" cannot be created.' |
303 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
304 |
WRITE(msgBuffer,*) |
305 |
&' Only ',maxTimers,' timers are allowed.' |
306 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
307 |
WRITE(msgBuffer,*) |
308 |
&'*******************************************************' |
309 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
310 |
GOTO 1000 |
311 |
905 CONTINUE |
312 |
WRITE(msgBuffer,*) |
313 |
&' ' |
314 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
315 |
WRITE(msgBuffer,*) |
316 |
&'*** WARNING WARNING WARNING WARNING WARNING WARNING ***' |
317 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
318 |
WRITE(msgBuffer,*) |
319 |
&'procedure: "',callProc,'".' |
320 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
321 |
WRITE(msgBuffer,*) |
322 |
&'Timer name is blank.' |
323 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
324 |
WRITE(msgBuffer,*) |
325 |
&' A name must be used with "START", "STOP" or "PRINT".' |
326 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
327 |
WRITE(msgBuffer,*) |
328 |
&'*******************************************************' |
329 |
CALL PRINT_MESSAGE(msgBuffer,standardMessageUnit,SQUEEZE_RIGHT,myThreadId) |
330 |
GOTO 1000 |
331 |
END |
332 |
|
333 |
CStartOfInterface |
334 |
SUBROUTINE TIMER_GET_TIME( |
335 |
O userTime, |
336 |
O systemTime, |
337 |
O wallClockTime ) |
338 |
C /==========================================================\ |
339 |
C | SUBROUTINE TIMER_GET_TIME | |
340 |
C | o Query system timer routines. | |
341 |
C |==========================================================| |
342 |
C | Routine returns total elapsed time for program so far. | |
343 |
C | Three times are returned that conventionally are used as | |
344 |
C | user time, system time and wall-clock time. Not all these| |
345 |
C | numbers are available on all machines. | |
346 |
C \==========================================================/ |
347 |
Real*8 userTime |
348 |
Real*8 systemTime |
349 |
Real*8 wallClockTime |
350 |
CEndOfInterface |
351 |
Real*4 ETIME, ACTUAL, TARRAY(2) |
352 |
EXTERNAL ETIME |
353 |
Real*8 wtime |
354 |
Real*8 MPI_Wtime |
355 |
EXTERNAL MPI_Wtime |
356 |
|
357 |
ACTUAL = ETIME(TARRAY) |
358 |
|
359 |
userTime = TARRAY(1) |
360 |
systemTime = TARRAY(2) |
361 |
#ifdef ALLOW_USE_MPI |
362 |
wtime = MPI_Wtime() |
363 |
wallClockTime = wtime |
364 |
#endif /* ALLOW_USE_MPI */ |
365 |
#ifndef ALLOW_USE_MPI |
366 |
wallClockTime = 0. |
367 |
#endif |
368 |
|
369 |
RETURN |
370 |
END |
371 |
|
372 |
CStartOfInterface |
373 |
SUBROUTINE TIMER_PRINTALL( myThreadId ) |
374 |
C /==========================================================\ |
375 |
C | SUBROUTINE TIMER_PRINTALL | |
376 |
C | o Print timer information | |
377 |
C |==========================================================| |
378 |
C | Request print out of table of timing from all timers. | |
379 |
C \==========================================================/ |
380 |
INTEGER myThreadId |
381 |
CEndOfInterface |
382 |
C Print out value for every timer. |
383 |
C |
384 |
CALL TIMER_CONTROL( ' ', 'PRINTALL', 'TIMER_PRINTALL' , myThreadId ) |
385 |
C |
386 |
RETURN |
387 |
END |
388 |
C*********************************************************************** |
389 |
SUBROUTINE TIMER_START ( string , myThreadId ) |
390 |
C Return start timer named "string". |
391 |
CHARACTER*(*) string |
392 |
INTEGER myThreadId |
393 |
C |
394 |
CALL TIMER_CONTROL( string, 'START', 'TIMER_START' , myThreadId) |
395 |
C |
396 |
RETURN |
397 |
END |
398 |
C*********************************************************************** |
399 |
SUBROUTINE TIMER_STOP ( string , myThreadId) |
400 |
C Return start timer named "string". |
401 |
CHARACTER*(*) string |
402 |
INTEGER myThreadId |
403 |
C |
404 |
CALL TIMER_CONTROL( string, 'STOP', 'TIMER_STOP' , myThreadId ) |
405 |
C |
406 |
RETURN |
407 |
END |
408 |
C*********************************************************************** |