My installer needs to change one line in a rather large (500Kb, about 11k lines) json file. I'm using AdvReplaceInFile to find and replace the occurrence.
Problem is, it takes about 3~ seconds to finish the job. I can kill the application using the same file, but if it's restarted automatically within that time window, even though the replace is successful, the process will overwrite the file again later on.
I'm assuming the algorithm responsible for search & replace is what's causing the slowdown.
How can I make it go faster other than writing my own DLL plugin to do so?
NSIS code was never designed to be fast and any call to a Int* function involves at least 2 string to number conversions etc.
Using a slightly slower version of the function you can prevent other processes from opening the file for writing:
Function AdvReplaceInFile
Exch $0 ;file to replace in
Exch
Exch $1 ;number to replace after
Exch
Exch 2
Exch $2 ;replace and onwards
Exch 2
Exch 3
Exch $3 ;replace with
Exch 3
Exch 4
Exch $4 ;to replace
Exch 4
Push $5 ;minus count
Push $6 ;universal
Push $7 ;end string
Push $8 ;left string
Push $9 ;right string
Push $R0 ;file1
Push $R1 ;file2
Push $R2 ;read
Push $R3 ;universal
Push $R4 ;count (onwards)
Push $R5 ;count (after)
Push $R6 ;temp file name
GetTempFileName $R6
FileOpen $R1 $0 a ;file to search in
FileOpen $R0 $R6 a ;temp file
StrLen $R3 $4
StrCpy $R4 -1
StrCpy $R5 -1
loop_read:
ClearErrors
FileRead $R1 $R2 ;read line
IfErrors exit
StrCpy $5 0
StrCpy $7 $R2
loop_filter:
IntOp $5 $5 - 1
StrCpy $6 $7 $R3 $5 ;search
StrCmp $6 "" file_write1
StrCmp $6 $4 0 loop_filter
StrCpy $8 $7 $5 ;left part
IntOp $6 $5 + $R3
IntCmp $6 0 is0 not0
is0:
StrCpy $9 ""
Goto done
not0:
StrCpy $9 $7 "" $6 ;right part
done:
StrCpy $7 $8$3$9 ;re-join
IntOp $R4 $R4 + 1
StrCmp $2 all loop_filter
StrCmp $R4 $2 0 file_write2
IntOp $R4 $R4 - 1
IntOp $R5 $R5 + 1
StrCmp $1 all loop_filter
StrCmp $R5 $1 0 file_write1
IntOp $R5 $R5 - 1
Goto file_write2
file_write1:
FileWrite $R0 $7 ;write modified line
Goto loop_read
file_write2:
FileWrite $R0 $R2 ;write unmodified line
Goto loop_read
exit:
FileSeek $R1 0 SET
!if "${NSIS_PTR_SIZE}" > 4
System::Call 'kernel32::SetEndOfFile(p$R1)'
!else
System::Call 'kernel32::SetEndOfFile(i$R1)'
!endif
FileSeek $R0 0 SET
ClearErrors
mov_loop:
FileRead $R0 $R2
IfErrors mov_done
FileWrite $R1 $R2
Goto mov_loop
mov_done:
FileClose $R0
FileClose $R1
SetDetailsPrint none
Delete $R6
SetDetailsPrint lastused
Pop $R6
Pop $R5
Pop $R4
Pop $R3
Pop $R2
Pop $R1
Pop $R0
Pop $9
Pop $8
Pop $7
Pop $6
Pop $5
;These values are stored in the stack in the reverse order they were pushed
Pop $0
Pop $1
Pop $2
Pop $3
Pop $4
FunctionEnd
The only way to actually make it faster would be to call a external program or write a plugin...
Related
Error:
spim: (parser) Register 1 is reserved for assembler on line 1 of file /Users/zhangchen/Desktop/1.asm
nor $1, $0,$0; #$1=FFFF_FFFF
enter image description here
nor $1, $0,$0; #$1=FFFF_FFFF
sltu $2, $0, $1; #$2=0000_0001
add $3, $2, $2; #$3=0000_0002
add $4, $3, $2; #$4=0000_0003
add $5, $4, $3; #$5=0000_0005
add $6, $5, $3; #$6=0000_0007
sllv $7, $6, $2; #$7=0000_000E
add $9, $5, $6; #$9=0000_000C
sllv $8, $6, $9; #$8=0000_7000
xor $9, $1, $8; #$9=FFFF_8FFF
add $10, $9, $1; #$10=FFFF_8FFE
sub $11, $8, $7; #$11=0000_6FF2
sub $12, $7, $8; #$12=FFFF_900E
and $13, $9, $12; #$13=FFFF_800E
or $14, $9, $12; #$14=FFFF_9FFF
or $15, $6, $7; #$15=0000_000F
nor $16, $6, $7; #$16=FFFF_FFF0
add $17, $7, $3; #$17=0000_0010
sllv $18, $8, $17; #$18=7000_0000
sllv $19, $3, $17; #$19=0002_0000
sllv $20, $19, $7; #$20=8000_0000
add $21, $20, $1; #$21=7FFF_FFFF
or $22, $18, $21; #$22=7FFF_FFFF
add $23, $20, $22; #$23=FFFF_FFFF
sub $24, $20, $22; #$24=0000_0001
sub $25, $22, $20; #$25=FFFF_FFFF
xor $26, $18, $1; #$26=8FFF_FFFF
sltu $27, $22, $20; #$27=0000_0001
sltu $28, $26, $20; #$28=0000_0000
add $29, $22, $2; #$29=8000_0000
sub $30, $20, $2; #$30=7FFF_FFFF
add $31, $11, $26; #$30=9000_6FF1
The error means exactly what it says. Register $1 is also know as $at (as in Assembler Temporary), and may be used by the assembler e.g. to hold intermediate results in code generated from pseudo-instructions.
You should generally not use $1 in your own code. It may be allowed by some assemblers, at least if you disable support for pseudo-instructions. Whether QtSPIM supports that, I don't know.
This code comes from a Chinese textbook, which points out that if you want to run this code normally, you need to set the Simulator mode to bare machine mode first, you can set this in Simulator-Settings.
By the way, this code has some errors(such as the value of #21 overflowed).
(Book's name is Experimental Instructions on Computer Composition Principle and System Structure(Second Edition))
Here is the code I tried
foreach_in_collection t1 [ ga ...] {
set f1 [ga $t1 type_name]
set f2 [ga $t1 bb]
puts $f1
puts $f2
puts $file1 $f1 $f2
}
I want to write two output $f1 and $f2 to $file1, please let me know
above puts $file1 [list $f1 $f2] also doesn't works..
Try this:
set fd [open out.txt w]
foreach {x y} [list a b c d e] {
puts x=$x
puts y=$y
puts $fd $x
puts $fd $y
}
close $fd
Output file (before):
$ cat out.txt
cat: out.txt: No such file or directory
$
Execution output:
$ tclsh foo.tcl
x=a
y=b
x=c
y=d
x=e
y=
$
Output file (after):
$ cat out.txt
a
b
c
d
e
$
Sharad's solution is perfect. There is one more way if you want to try.
SBORDOLO-M-V1VG:EXPERIMENT sbordolo$ cat a11
exec rm -rf out1.txt ;# No need to worry about file is there or not.
exec touch out1.txt
exec chmod 777 out1.txt ;#You can give a write permission here. I have given 777 just like that
foreach {x y} [list a b c d e f] {
puts x=$x
puts y=$y
exec echo $x >> out1.txt
exec echo $y >> out1.txt
}
When you run the script:
SBORDOLO-M-V1VG:EXPERIMENT sbordolo$ tclsh a11
x=a
y=b
x=c
y=d
x=e
y=f
SBORDOLO-M-V1VG:EXPERIMENT sbordolo$ cat out1.txt
a
b
c
d
e
f
Several times I run into mentioning that it is best to put script into proc in order to boost run time performance, e.g. this answer has the following:
That is one reason for the advices to put all your code inside procedures (they get byte-compiled that way)
Something does not click in me.
Just as described in the answer, the first time a script runs, there is a check if a command can be byte-code compiled, if it is, then it is compiled. This makes total sense. But I do not see how "proc" plays an important role. E.g. compare the following 2 scripts:
set v [concat [lindex $::argv 1] [lindex $::argv 2]]
myCmd $v
and
proc p1 {v1 v2} {
set v [concat $v1 $v2]
return [myCmd $v]
}
p1 [lindex $::argv 1] [lindex $::argv 2]
My high level interpretation of the 2 scripts tells the following:
In running either script the first time, "set", "concat", "lindex" and "return" commands are compiled
The second script also has "proc" compiled.
"myCmd" is not compiled in either script
Subsequent running of either script runs the bycode except "myCmd".
So what is the advantage of "proc"?
I did run dissamble on the scripts:
The first script:
ByteCode 0x0x83fc70, refCt 1, epoch 3, interp 0x0x81d680 (epoch 3)
Source "set v [concat [lindex $::argv 1] [lindex $::argv 2]]\nmy"
Cmds 5, src 61, inst 50, litObjs 4, aux 0, stkDepth 4, code/src 0.00
Commands 5:
1: pc 0-41, src 0-51 2: pc 2-39, src 7-50
3: pc 4-20, src 15-30 4: pc 21-37, src 34-49
5: pc 42-48, src 53-60
Command 1: "set v [concat [lindex $::argv 1] [lindex $::argv 2]]"
(0) push1 0 # "v"
Command 2: "concat [lindex $::argv 1] [lindex $::argv 2]"
(2) push1 1 # "concat"
Command 3: "lindex $::argv 1"
(4) startCommand +17 1 # next cmd at pc 21
(13) push1 2 # "::argv"
(15) loadScalarStk
(16) listIndexImm 1
Command 4: "lindex $::argv 2"
(21) startCommand +17 1 # next cmd at pc 38
(30) push1 2 # "::argv"
(32) loadScalarStk
(33) listIndexImm 2
(38) invokeStk1 3
(40) storeScalarStk
(41) pop
Command 5: "myCmd $v"
(42) push1 3 # "myCmd"
(44) push1 0 # "v"
(46) loadScalarStk
(47) invokeStk1 2
(49) done
The second script:
ByteCode 0x0xc06c80, refCt 1, epoch 3, interp 0x0xbe4680 (epoch 3)
Source "proc p1 {v1 v2} {\n set v [concat $v1 $v2]\n return"
Cmds 4, src 109, inst 50, litObjs 5, aux 0, stkDepth 4, code/src 0.00
Commands 4:
1: pc 0-10, src 0-67 2: pc 11-48, src 69-108
3: pc 13-29, src 73-88 4: pc 30-46, src 92-107
Command 1: "proc p1 {v1 v2} {\n set v [concat $v1 $v2]\n return"
(0) push1 0 # "proc"
(2) push1 1 # "p1"
(4) push1 2 # "v1 v2"
(6) push1 3 # "\n set v [concat $v1 $v2]\n return ["
(8) invokeStk1 4
(10) pop
Command 2: "p1 [lindex $::argv 1] [lindex $::argv 2]"
(11) push1 1 # "p1"
Command 3: "lindex $::argv 1"
(13) startCommand +17 1 # next cmd at pc 30
(22) push1 4 # "::argv"
(24) loadScalarStk
(25) listIndexImm 1
Command 4: "lindex $::argv 2"
(30) startCommand +17 1 # next cmd at pc 47
(39) push1 4 # "::argv"
(41) loadScalarStk
(42) listIndexImm 2
(47) invokeStk1 3
(49) done
So script 2 does have 1 less TCL command, but both scripts have 49 byte code commands.
Finally the running test, I comment out "myCmd" because I actually do not have such extension. Here is the result:
% time {source 1.tcl} 10000
242.8156 microseconds per iteration
% time {source 2.tcl} 10000
257.9389 microseconds per iteration
So the proc version is even slower.
What do I miss? Or rather, what is the exact understanding of proc and performance?
The really big reason that putting things in a procedure matters is that procedures have a local variable table. Variables in the LVT can be accessed by numerical index, which is stupendously faster than the alternative (a lookup via a hash table, even though Tcl's got an extremely fast hash table implementation). It doesn't make much difference for a one-off call, but with repeated calls or a loop, the performance differences rapidly add up to something significant. This can quite easily make the extra cost of the extra compilation and stack frame management (procedures aren't free to enter, though we try to keep them cheap) basically irrelevant in real scripts.
And yes, Tcl actually bytecode-compiles everything. It's just that it often generates sub-optimal bytecode outside of procedure(-like context)s; in the limit case for suboptimality, all the bytecode is doing is assembling arguments into a list, doing a dynamic command invoke, and routing the result.
(It's important when reading Tcl's disassembled bytecode to remember that the costs of particular bytecodes are not all the same. You cannot just count the number of instructions to work out the cost in any useful way. For example, push1 is very cheap but invokeStk1 is potentially very costly. Another example, loadScalarStk is usually much more expensive than loadScalar1; the latter is used inside procedures only.)
The following two scripts demonstrate the performance gain due to usage of procs. In the second script the internal loop is extracted into a proc, leading to a 5x speedup.
without_proc.tcl
#!/usr/bin/env tclsh
set sum 0
set n 10000
set k 100
for { set i 0 } { $i < $k } { incr i } {
set s 0
for { set j 0 } { $j < $n } { incr j } {
set s [expr {$s + $j}]
}
set sum [expr {$sum + $s}]
}
puts "sum=$sum"
with_proc.tcl
#!/usr/bin/env tclsh
proc foo {n} {
set s 0
for { set j 0 } { $j < $n } { incr j } {
set s [expr {$s + $j}]
}
return $s
}
set sum 0
set n 10000
set k 100
for { set i 0 } { $i < $k } { incr i } {
set s [foo $n]
set sum [expr {$sum + $s}]
}
puts "sum=$sum"
Benchmark:
$ tclsh
% time {source with_proc.tcl} 1
sum=4999500000
67482 microseconds per iteration
% time {source without_proc.tcl} 1
sum=4999500000
406557 microseconds per iteration
or
$ time tclsh with_proc.tcl
sum=4999500000
real 0m0.089s
user 0m0.080s
sys 0m0.004s
$ time tclsh without_proc.tcl
sum=4999500000
real 0m0.401s
user 0m0.388s
sys 0m0.016s
Making A Code Which Will Close All opened Browser in nsis.
var hadBrowsers
Function closeAllBrowsers
FindWindow $0 "IEFrame"
IntCmp $0 0 0 closeAll
FindWindow $0 "MozillaUIWindowClass"
IntCmp $0 0 0 closeAll
FindWindow $0 "Chrome_WidgetWin_0"
IntCmp $0 0 done closeAll
closeAll:
MessageBox MB_OK "Please close your browsers or press ok to close them all automatically"
;Closing all IE Windows
loop:
FindWindow $0 "IEFrame"
IntCmp $0 0 skipIE
IsWindow $0 0 skipIE
System::Call 'user32::PostMessageA(i,i,i,i) i($0,${WM_CLOSE},0,0)'
StrCpy $hadBrowsers "1"
skipIE:
FindWindow $0 "MozillaUIWindowClass"
IntCmp $0 0 skipFF
IsWindow $0 0 skipFF
System::Call 'user32::PostMessageA(i,i,i,i) i($0,${WM_CLOSE},0,0)'
StrCpy $hadBrowsers "1"
skipFF:
FindWindow $0 "Chrome_WidgetWin_0"
IntCmp $0 0 skipGC
IsWindow $0 0 skipGC
System::Call 'user32::PostMessageA(i,i,i,i) i($0,${WM_CLOSE},0,0)'
StrCpy $hadBrowsers "1"
skipGC:
FindWindow $0 "IEFrame"
IntCmp $0 0 0 wait
FindWindow $0 "MozillaUIWindowClass"
IntCmp $0 0 0 wait
FindWindow $0 "Chrome_WidgetWin_0"
IntCmp $0 0 done wait
wait:
Sleep 100
goto loop
done:
;MessageBox MB_OK $hadBrowsers
StrCmp $hadBrowsers "1" 0 +1
Sleep 2000
FunctionEnd
this is not working .
Giving me some error.
I am trying to make a code in nsis which will close all opened browser and reopen them.
I got Answer
Section
Execwait '"$SYSDIR\taskkill.exe" /F /IM chrome.exe /T'
SectionEnd
we can kill any task.
im working on a project where im using NSI to create a setup myproject.exe,
everything works fine, but i want to dump the log that is displayed into a file, that also works fine, but my problem is i cant figure out when to call the log function
i have 12 sections in my script , i got the function to create the installation log from Dump log to file
!define PRODUCT_NAME "myApplication"
!define PRODUCT_VERSION "1.7.1"
!define LVM_GETITEMCOUNT 0x1004
!define LVM_GETITEMTEXT 0x102D
!include MUI2.nsh
Name "myExe"
OutFile "C:\myExe.exe"
InstallDir $PROGRAMFILES\myprojc
InstallDirRegKey HKCU "Software\myprojc" "Install_Dir"
!define MUI_ABORTWARNING
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\Folder-Options.ico"
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\orange-uninstall.ico"
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\Banner.bmp"
; Pages
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
;Languages
!insertmacro MUI_LANGUAGE "English"
BrandingText "myProj version 1.7.1"
;Here My section are decribed.. 12 of them
Section "main program and components" section1
;main promgram files are copied
SectionEnd
Section "other DLL" section2
;copy the DLLs
SectionEnd
Section "Text files" section3
;copy the required txt files
SectionEnd
;-....
;...
Section "install BDE admin" section12
;copy install BDE admin
;Calling the Dumplog function to create the log file
StrCpy $0 "$INSTDIR\Query\Presleyinstall.log"
Push $0
Call DumpLog
SectionEnd
;The function dumps the to a log file
Function DumpLog
Exch $5
Push $0
Push $1
Push $2
Push $3
Push $4
Push $6
FindWindow $0 "#32770" "" $HWNDPARENT
GetDlgItem $0 $0 1016
StrCmp $0 0 exit
FileOpen $5 $5 "w"
StrCmp $5 "" exit
SendMessage $0 ${LVM_GETITEMCOUNT} 0 0 $6
System::Alloc ${NSIS_MAX_STRLEN}
Pop $3
StrCpy $2 0
System::Call "*(i, i, i, i, i, i, i, i, i) i \
(0, 0, 0, 0, 0, r3, ${NSIS_MAX_STRLEN}) .r1"
loop: StrCmp $2 $6 done
System::Call "User32::SendMessageA(i, i, i, i) i \
($0, ${LVM_GETITEMTEXT}, $2, r1)"
System::Call "*$3(&t${NSIS_MAX_STRLEN} .r4)"
FileWrite $5 "$4$\r$\n"
IntOp $2 $2 + 1
Goto loop
done:
FileClose $5
System::Free $1
System::Free $3
exit:
Pop $6
Pop $4
Pop $3
Pop $2
Pop $1
Pop $0
Exch $5
FunctionEnd
Since i have called the dumplog function in section 12, the log file is only create when/if the section is selected by the user, if the section 12 is not selected then the dumplog function wont be called and log file will not be created,
so please tell me how do i call the dumplog function irrespective of the section is selected or not by the user, That is call the dumplog when the installation is complete.
EDIT
i can also try to create a section "Create installer log" and check it and disable like
Section "Create installer log" section13
SectionIn RO
StrCpy $0 "$INSTDIR\myinstall.log"
Push $0
Call DumpLog
SectionEnd
this
but is there a way to call the dumplog function without section, and at the end of the installation?
If you don't give the section a name (or start the name with -) then it will not be displayed on the components page:
Section "Foobar"
;Install the optional Foobar
SectionEnd
Section
;Hidden section
Section
You can even put it in callback functions.
Function .onInstSuccess
;..
Call DumpLog
;..
FunctionEnd
Function .onInstFail
;..
Call DumpLog
;..
FunctionEnd