虛擬記憶體對 OI 的影響

caijianhong發表於2024-10-25

假想你寫了這麼四段程式碼:

#include <vector>
#include <bits/stdc++.h>
using namespace std;
std::vector<int> vec;
//int a[100000010];
int main() { freopen("a.out", "w", stdout), cout << 1 << endl;}//, vec.resize(1e8); }
#include <vector>
#include <bits/stdc++.h>
using namespace std;
std::vector<int> vec;
int a[100000010];
int main() { freopen("a.out", "w", stdout), cout << 1 << endl;}//, vec.resize(1e8); }
#include <vector>
#include <bits/stdc++.h>
using namespace std;
std::vector<int> vec;
//int a[100000010];
int main() { freopen("a.out", "w", stdout), vec.resize(1e8),cout << 1 << endl ; }
#include <vector>
#include <bits/stdc++.h>
using namespace std;
std::vector<int> vec;
int a[100000010];
int main() { freopen("a.out", "w", stdout), vec.resize(1e8), cout << 1 << endl; }

已知 \(10^8\)int 需要佔用記憶體 400M,那麼就是說,在記憶體限制 600M 的情況下,前三個程式都能正確輸出 1,第四個程式因為有 400M 的陣列和 400M 的 vector 而因 MLE 致死。使用 NOI Linux 2.0 的 arbiter_local 進行測評,結果也確實如此。

但實際上在本地執行第四段程式的時候,無法發現使用記憶體超過 600M 的事實。一般有三種手段檢查記憶體,第一種是用全域性變數結尾和開頭兩個變數地址相減的辦法測算空間(這樣會測少(標準庫自身會使用一部分靜態空間),並且這種寫法是 UB)。是第二種是使用 GNU size 測量靜態空間,第三種是使用 GNU time 測量最大常駐記憶體大小。使用後兩種方法,可以發現都不超過 400M:

minni@LAPTOP-VTBPQCQP:~/test$ make sizetest
g++ sizetest.cpp -o sizetest -O2 -g -DNF -DLOCAL -fsanitize=undefined,address -Wall -std=c++14
sizetest.cpp: In function ‘int main()’:
sizetest.cpp:6:21: warning: ignoring return value of ‘FILE* freopen(const char*, const char*, FILE*)’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
    6 | int main() { freopen("a.out", "w", stdout), vec.resize(1e8), cout << 1 << endl; }
      |              ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
minni@LAPTOP-VTBPQCQP:~/test$ cat sizetest.cpp
#include <vector>
#include <bits/stdc++.h>
using namespace std;
std::vector<int> vec;
int a[100000010];
int main() { freopen("a.out", "w", stdout), vec.resize(1e8), cout << 1 << endl; }
minni@LAPTOP-VTBPQCQP:~/test$ size sizetest
   text    data     bss     dec     hex filename
  51392   29880 400001544       400082816       17d8c780        sizetest
minni@LAPTOP-VTBPQCQP:~/test$ \time ./sizetest
0.12user 0.09system 0:00.22elapsed 98%CPU (0avgtext+0avgdata 402636maxresident)k
0inputs+8outputs (0major+15370minor)pagefaults 0swaps

而事實上他會使用到 800M,我們需要引入虛擬記憶體的概念:https://www.pengrl.com/p/21292/。測量虛擬記憶體峰值使用 system("grep VmPeak /proc/$PPID/status")(程式末尾),這是 Arbiter 所測量的空間大小。

為了能在本地發現此問題,可以使用 ulimit -v 限制虛擬記憶體大小,如這裡限制為 600M 就寫

ulimit -v 600000

注意單位是 KB,這樣程式就能正常報錯了:

minni@LAPTOP-VTBPQCQP:~/test$ g++ sizetest.cpp -o sizetest -O2 -g -DNF -DLOCAL -Wall -std=c++14 && ./sizetest
sizetest.cpp: In function ‘int main()’:
sizetest.cpp:6:21: warning: ignoring return value of ‘FILE* freopen(const char*, const char*, FILE*)’ declared with attribute ‘warn_unused_result’ [-Wunused-result]
    6 | int main() { freopen("a.out", "w", stdout), vec.resize(1e8), cout << 1 << endl; }
      |              ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
已中止 (核心已轉儲)

對照實驗:刪去 vectorint[100000010] 的其中一個,程式都不會報錯,具體的測試可以自己做。

以上,警醒我們限制虛擬記憶體以防止最終評測的 MLE。可使用 ulimit -v <實際空間限制> 解決該問題。

ref:https://www.luogu.com/article/epn32t2i

相關文章