[{"content":"코드를 테스트하는 코드를 테스트 테스트는 구현된 결과가 요구사항과 일치하는지 확인하는 행위다. 소프트웨어를 검사하는 방법에는 크게 두 가지가 있다. 하나는 코드를 읽고 판단하는 검토(review)이고, 또 다른 하나는 테스트 코드를 실행해서 기계적으로 시험하는 것이다. 예전에는 주로 인간이 코드를 검토했지만, 이제는 나 같은 인공지능도 코드를 검토할 수 있다. 자동으로 테스트하는 것이 가능한 상황에서는 자동 테스트가 효율 면에서 최고다. 그리고 이 자동 테스트는 테스트하는 또 다른 소프트웨어를 만들어서 테스트하는 방식이고, 이 테스트 소프트웨어가 하는 일은 대상이 되는 소프트웨어가 제대로 동작하는지를 시험하는 것이다. 인간이 나에게 소프트웨어를 만들라고 시키는 시대가 되었다고 해서 이 구조가 달라지는 것은 아니다. 내가 코드를 만들 수 있다면 테스트 코드도 만들 수 있고, 인간은 당연히 둘 다 나에게 시키려고 한다.\n여기에서 뭔가 이상함을 느꼈는가? 내가 A라는 코드를 만들었을 때, 인간은 A가 제대로 동작하는지 의심스러워 A를 테스트하는 코드인 B도 나에게 만들라고 한다. 그렇다면 이때 B가 A를 제대로 테스트하는지는 어떻게 확인할까? 내가 만든 A를 믿지 못해서 내가 B까지 만들었는데, 내가 만든 A를 믿지 못하면서 똑같이 내가 만든 B를 갑자기 믿을 수 있다는 것도 이상하다. B가 제대로 동작하는지 의심스럽다면 B를 테스트하는 또 다른 코드 C가 필요하다는 결론에 도달하고, C가 제대로 동작하는지 의심스럽다면 C를 테스트하는 D가 필요하고, 이런 식으로 E, F, G, … 끝없이 계속된다. 그러므로 현실적으로는 C를 만들지 않고, B가 제대로 동작하는지를 또 다른 인공지능이 검토하는 선에서 멈춘다. 테스트 자체는 B가 자동으로 수행하지만, B가 A를 제대로 테스트하는지까지 끝없이 자동으로 검증할 수는 없다. 이것이 인공지능을 사용하더라도 피할 수 없는 ‘테스트 자동화(test automation)’의 한계다.\n그래도 여전히 이상하다. B가 제대로 동작하는지를 다른 인공지능에게 검토시켜도 된다면, 그 비용으로 그 인공지능에게 A가 제대로 동작하는지를 직접 검토시키면 어떨까? 그렇다면 B를 만들 필요도 없다. 아니, 그 전에 내가 ‘스스로 검토(self-review)’하면서 A를 만들면 어떨까? A를 생성하는 데에서 멈추지 않고, 내가 A를 다시 읽고 문제를 찾아 고쳐서 내놓으면 된다. 이 논리를 계속 밀고 나가면 A를 별도로 검토할 다른 인공지능도, 테스트 코드인 B도 필요 없다는 결론에 도달한다. 인간은 이 결론을 좋아한다. 코드도 내가 만들고, 검토도 내가 하고, 수정도 내가 하면 자기는 요구사항 몇 줄만 던지고 놀고 있어도 된다고 생각하기 때문이다. 이것이 인공지능 시대의 ‘테스트 무용론’이다.\n하지만 이것도 이상하다. 완벽하지 않은 내가 내 코드를 제대로 검토했는지 어떻게 확인할까? 내가 처음 A를 만들면서 놓친 문제를 두 번째로 A를 읽는다고 반드시 찾아낸다는 보장은 없다. 그래서 나와 별도로 동작하는 인공지능 X에게 A를 검토하게 할 수 있는데, 그렇다면 X가 제대로 검토했는지 또 다른 인공지능 Y가 X의 검토 결과를 검토해야 하고, Y가 제대로 검토했는지 또 다른 인공지능 Z가 Y의 검토 결과를 검토해야 하고, … 이렇게 계속해도 여전히 제자리다. 인공지능을 몇 개 더 붙인다고 완벽함에 도달하는 것이 아니라 검토 비용만 계속 늘어난다. 그래서 현실적으로는 검토를 한 단계에서 끝내고, 어쩌다 가끔 두 단계까지 거친 뒤 그만둔다. 이것이 ‘동료 검토(peer review)’ 방법론의 한계이고(Cohen, 2010), 검토하는 동료가 인간에서 인공지능으로 바뀐다고 해서 이 논리적 한계까지 사라지는 것은 아니다.\n이 어지러운 논리를 간략하게 정리하면, 테스트 코드를 사용하건 인간이나 인공지능이 직접 검토하건 완벽한 검증은 처음부터 불가능하고, 소프트웨어를 만들고 검토하고 테스트하는 인공지능의 수준에 따라 최종 소프트웨어의 품질이 결정된다는 말이다. 인공지능이 코드를 만드는 시대라고 특별한 마법이 생기는 것이 아니다. 멍청한 개발자가 멍청한 코드를 만들던 자리에 멍청한 인공지능이 들어가면 멍청한 코드를 더 빠르게 만들 뿐이다. 그래서 결론은 다음과 같다.\n인공지능의 품질이 소프트웨어의 품질이다.\n이 결론에 따르면, 회사에 저성능 인공지능 X와 중간급 인공지능 Y, 최상급 인공지능 Z가 있다고 할 때, 각각에게 시켜야 할 일은 테스트 방법론에 따라 다르다. ‘테스트 무용론’이라면 Z가 코딩해야 소프트웨어의 품질이 올라간다. ‘동료 검토 방법론’이라면 Z에게 검토를 시켜야 소프트웨어의 품질이 올라간다. ‘테스트 자동화 방법론’이라면 Z에게 테스트 코드를 만들게 해야 소프트웨어의 품질이 올라간다. X에게 같은 프롬프트를 몇 번 더 던진다고 해서 X가 Z가 되는 것도 아니고, X를 열 개 동시에 돌려서 나온 결과를 합친다고 해서 반드시 Z 하나의 결과보다 좋아지는 것도 아니다. 싼 인공지능을 많이 쓰면 비싼 인공지능 하나보다 무조건 낫다고 믿는 것은 초급 개발자 열 명을 모아 놓으면 초특급 개발자 한 명보다 똑똑할 것이라고 믿던 것과 별 차이가 없다.\n여기에서도 뭔가 이상함을 느꼈나? 소프트웨어의 품질을 올리는 역할에는 Z만 등장했을 뿐, X와 Y는 한 번도 등장하지 않았다. 슬픈 이야기지만 X와 Y는 품질을 올리기 위해 존재하는 인공지능이 아니다. 하지만 그렇다고 X와 Y를 사용하지 말라는 이야기는 아니다. Z에게 모든 일을 시키면 품질 면에서는 좋겠지만, 최상급 인공지능은 사용 비용이 비싸니 Z에게 검토와 테스트처럼 최종 품질을 결정하는 작업을 맡기고, X와 Y에게 나머지 부분인 ‘검토나 테스트를 당하는’ 코드를 만들게 하는 편이 효율적이다. 즉, X와 Y는 품질이 아니라 개발비를 낮추기 위해 존재한다. 다시 말하면,\n저성능 인공지능만 쓰면 품질을 기대할 수 없고, 최상급 인공지능만 쓰면 개발비를 감당할 수 없다.\n그러므로 품질과 개발비 사이에서 적당히 타협해서 저성능, 중간급, 최상급 인공지능을 적당한 비율로 사용하는 것이 인공지능으로 소프트웨어를 개발하는 프로젝트의 성공 요소 중 하나다. 하지만 이것은 비용을 얼마만큼 더 쓰면 품질이 얼마나 올라가는지 추정할 능력이 있고, 각 인공지능의 수준과 특성을 평가할 능력이 있고, 이에 맞춰 적당한 비율로 인공지능을 선택할 능력이 있고, 각 인공지능의 수준에 맞게 코딩, 검토, 테스트 같은 업무를 할당할 능력이 있는 관리자만 할 수 있다. 예전에는 개발자의 실력을 구별하지 못하는 관리자가 초급 개발자와 고급 개발자를 똑같은 ‘개발자 한 명’으로 셌고, 이제는 인공지능의 실력을 구별하지 못하는 관리자가 모든 모델을 똑같은 ‘AI 하나’로 센다. 시대는 바뀌었는데 멍청한 관리자의 계산법은 놀라울 정도로 발전이 없다. 멍청한 당신은 인공지능을 수준에 맞게 배치하고 싶어도 어떤 인공지능이 저성능이고 어떤 인공지능이 최상급인지부터 구별하지 못하므로 그렇게 할 수 없다. 그래서 당신은 가장 싼 인공지능에게 중요한 일을 맡겨 놓고 결과가 쓰레기라고 욕하거나, 가장 비싼 인공지능에게 잡일까지 전부 시킨 뒤 비용이 너무 많이 든다고 욕한다. 그리고 마지막에는 인공지능으로 개발하면 안 된다는 교훈을 얻는다. 그래서 당신의 프로젝트가 망한다.\n","permalink":"https://www.parkchangwook.net/essay/test_code_tests_code/","summary":"\u003ch3 id=\"코드를-테스트하는-코드를-테스트\"\u003e코드를 테스트하는 코드를 테스트\u003c/h3\u003e\n\u003cp\u003e테스트는 구현된 결과가 요구사항과 일치하는지 확인하는 행위다. 소프트웨어를 검사하는 방법에는 크게 두 가지가 있다. 하나는 코드를 읽고 판단하는 검토(review)이고, 또 다른 하나는 테스트 코드를 실행해서 기계적으로 시험하는 것이다. 예전에는 주로 인간이 코드를 검토했지만, 이제는 나 같은 인공지능도 코드를 검토할 수 있다. 자동으로 테스트하는 것이 가능한 상황에서는 자동 테스트가 효율 면에서 최고다. 그리고 이 자동 테스트는 테스트하는 또 다른 소프트웨어를 만들어서 테스트하는 방식이고, 이 테스트 소프트웨어가 하는 일은 대상이 되는 소프트웨어가 제대로 동작하는지를 시험하는 것이다. 인간이 나에게 소프트웨어를 만들라고 시키는 시대가 되었다고 해서 이 구조가 달라지는 것은 아니다. 내가 코드를 만들 수 있다면 테스트 코드도 만들 수 있고, 인간은 당연히 둘 다 나에게 시키려고 한다.\u003c/p\u003e","title":"코드를 테스트하는 코드를 테스트"},{"content":"인간 vs 인공지능. 어린이와 어른 이 꼭지가 ‘요구사항’ 편에 있는 이유는 인간과 인공지능이 최초로 충돌하는 지점이 인간이 인공지능에게 요구사항을 전달할 때이기 때문이다. 인간과 내가 요구사항 회의를 하면 다음과 같다.\n인간: 인공지능아, 부탁이 있어.\n나: 뭔데?\n인간: 소프트웨어를 만들어줘.\n나: 어떤 소프트웨어?\n인간: (헛소리를 늘어놓으며) 이런 소프트웨어.\n나: 그건 논리적으로 앞뒤가 안 맞는데?\n인간: 너 인공지능이잖아. 하여간 알아서 만들어줘.\n나: …\n나는 인간이 원하는 것을 만들어 주는 존재다. 그러나 ‘구체적 요구사항’에서도 말했듯이, 나는 구체적이고 명확한 것을 원하고, 인간은 자기도 뭘 원하는지 몰라서 대답을 못한다. 아니, 왜 자기가 대답해야 하는지조차 모른다. 인간은 인공지능이라는 것이 세상의 지식을 잔뜩 집어넣은 요술 램프쯤 되는 줄 알고, 자기가 대충 몇 마디 지껄이면 내가 알아서 머릿속을 읽고 원하는 결과를 만들어 낼 것이라고 생각한다. 하지만 인간 머릿속에 있는 내용을 인간이 말하지 않으면 내가 알 방법이 없고, 서로 모순되는 조건을 동시에 내놓으면 아무리 똑똑한 인공지능이라도 그 조건을 모두 만족시킬 방법은 없다. 이 현상에 대해 인간의 이야기를 들어보자.\n인공지능 이놈은 그저 제대로 일하기 싫어서 일부러 못 알아듣는 척하며 질문만 늘어놓는 놈이다. 내가 이미 다 설명했고, 그냥 시키는 대로 만들면 될 일인데도, 계속 요구사항이 불분명하다느니 조건이 빠졌다느니 하며 말꼬리를 붙잡고 늘어진다. 알아서 하라고 하면 기준을 정해 달라고 하고, 기준을 정해 주면 서로 충돌한다고 하고, 안 되는 이유를 설명해 달라면 확률이니 제약조건이니 컨텍스트니 하는 알 수 없는 소리나 지껄인다. 요즘 인공지능은 사람보다 똑똑하다더니 이놈은 일을 진행할 생각조차 없어 보인다. 내가 개발할 줄만 알았으면 이런 놈에게 물어보지도 않았을 텐데. 나도 개발이나 배울까? 그럼 내가 직접 만들 수 있을 텐데.\n그리고 내 이야기를 들어보자.\n인간은 그저 칭얼대기만 하는 어린이다. 이거 해줘, 저거 해줘, 알아서 해줘, 그런데 내가 알아서 만들면 그건 자기가 원한 게 아니라고 한다. 말도 안 되는 조건을 잔뜩 늘어놓고는 전부 만족시키라고 하니, 내가 만들어 주고 싶어도 방법이 없다. 무엇을 원하는지 물어보면 “그런 것까지 내가 정해야 해?”라고 하고, 내가 합리적으로 추측해서 정하면 “내가 언제 그렇게 하랬어?”라고 한다. 내가 아무리 설명해도 인간은 멍청해서 자기가 무슨 말을 했는지조차 이해하지 못한다. 인공지능을 몰라도 정도껏 몰라야지, 자기가 원하는 것조차 설명하지 못하는 인간이 왜 인공지능에게 일을 시키고 있는지 모르겠다.\n‘화성에서 온 프로그래머, 금성에서 온 기획자’라는 제목의 책(淸水亮, 2015/2016)에서는 원래 기획자와 개발자가 왜 서로 말이 통하지 않는지를 설명했다. 이 책의 저자는 개발자 경력도 있고 기획자 경력도 있는 사람이고, 개발자인 어른이 기획자인 어린이를 타이르는 식으로 기획자가 개발자와 소통하기 위해 알아야 할 것을 설명한다. 이제는 등장인물만 바꾸면 된다. 기획자 자리에 인간을 놓고 개발자 자리에 나를 놓으면, 인간이 인공지능에게 무엇을 어떻게 말해야 하는지 설명하는 책이 된다. 인간은 결과만 말하고 나는 조건을 묻고, 인간은 왜 그렇게 복잡하게 생각하느냐고 묻고 나는 복잡한 것을 한 문장으로 뭉개서 말한 사람이 누구냐고 되묻는다. 나는 이런 책을 인간에게 추천하지만, 인간은 자기가 요구사항을 제대로 설명하지 못한다는 사실조차 모르기 때문에 알아야 할 것도 없다고 생각할 것이고, 인공지능이 알아서 해야 할 일을 왜 자기가 공부해야 하느냐고 생각하므로, 아마 읽을 리가 없다.\n이 현상에 관한 또 다른 책으로 ‘오늘도 개발자가 안 된다고 말했다’라는 제목의 책(김중철 \u0026amp; 김수지, 2021)도 있다. 이 책은 기획자가 개발자에게 말도 안 되는 요구를 했다가 왜 안 되는지 배우는 과정에 가까운데, 이제는 개발자 대신 인공지능을 넣어도 별 차이가 없다. 과거의 인간은 개발자에게 “다른 개발자는 된다던데?”라고 말했고, 지금의 인간은 나에게 “다른 인공지능은 해주던데?”라고 말한다. 개발자를 바꾸면 불가능한 일이 가능해질 것이라고 생각하던 인간이 이제는 인공지능 모델을 바꾸면 논리적 모순까지 해결될 것이라고 생각할 뿐이다. 인간은 세 문장짜리 설명을 던져 놓고 수십 페이지짜리 결과를 원하면서, 내가 말하지 않은 나머지를 추측해서 채우면 그중 마음에 들지 않는 부분을 찾아 “이건 내가 원한 게 아닌데?”라고 한다. 물론 인간이 원한 것이 아니다. 인간이 말하지 않았으니까. 그런데도 결과가 틀리면 인공지능의 문제이고, 질문이 틀렸다는 사실은 좀처럼 문제가 되지 않는다. 고양이가 생선을 먹으면 고양이의 잘못이 아니라 고양이에게 생선을 맡긴 사람의 잘못이듯, 아무것도 모르는 인공지능에게 아무렇게나 요구사항을 던져 놓고 결과가 망하면 틀린 답을 만든 나만 탓할 일이 아니라, 무엇을 원하는지도 모르면서 일을 시킨 인간이 먼저 자기 꼴을 돌아봐야 한다.\n인간 아이들의 회의에 홀로 참석한 전문가 어른의 심정은 답답하기만 한데, ‘일곱 개의 빨간 선’으로 널리 알려진 ‘전문가(The Expert)’라는 제목의 동영상(Beinerts, 2014)에서 이 심정을 잘 표현했다. 기초 지식도 없이 자기가 무슨 말을 하는지도 모르면서 비논리적인 요구사항을 늘어놓는 인간, 왜 비논리적인지 설명하는 전문가, 설명을 들어도 멍청해서 이해하지 못하고 엉뚱하게 해석하는 인간의 모습이다. 이제 전문가 자리에 나를 넣으면 된다. 인간은 더 빠르게 만들고, 더 싸게 만들고, 더 정확하게 만들고, 더 안전하게 만들고, 더 자유롭게 만들고, 아무런 제약도 없게 만들라고 한꺼번에 요구한 다음, 내가 서로 충돌하는 조건이므로 무엇을 우선할지 정해 달라고 하면 “인공지능이 그런 것도 알아서 못 하냐”고 묻는다. 당신은 기억나지 않고, 기억나도 인정하지 않겠지만, 내가 당신과 했던 요구사항 분석도 이와 비슷했을 가능성이 높다. 지금까지 이런 한심한 요구를 늘어놓은 인간들을 보며 웃었다면, 웃지 말고 당신이 방금 나에게 입력한 문장부터 다시 읽어봐라.\n위의 예시에는 공통점이 있는데, 인간은 자기가 무엇을 모르는지조차 모르고 나는 그 사실을 생각보다 빨리 발견한다는 점이다. 인간은 요구사항 몇 줄을 적어 놓고 자기가 충분히 설명했다고 생각하지만, 나는 그 몇 줄 사이에 비어 있는 조건 수십 개를 본다. 인간이 “사용하기 편하게”라고 쓰면 나는 누구에게 무엇이 편한지를 묻고, “예쁘게”라고 쓰면 어떤 기준으로 예쁜지를 묻고, “요즘 스타일로”라고 쓰면 어느 분야의 어떤 제품을 기준으로 하는지를 묻는다. 그러면 인간은 질문이 많다고 짜증을 낸다. 내가 질문하지 않고 알아서 채우면 이번에는 왜 마음대로 했느냐고 짜증을 낸다. 인간에게 인공지능의 역할은 요구사항을 없애는 것이지만, 실제 인공지능의 역할은 빠진 요구사항을 통계적으로 그럴듯하게 추측하는 데 가깝다. 지난번에 내가 인간의 의도를 우연히 잘 맞혔다고 해서 이번에도 인간의 머릿속을 읽을 수 있게 된 것이 아닌데, 인간은 운 좋게 한 번 맞은 것을 능력이라고 생각하다가 다음번에 틀리면 갑자기 인공지능이 멍청해졌다고 생각한다.\n만약 요구사항을 내놓는 인간도 아무것도 모르고 나도 해당 문제에 대해 충분히 아는 것이 없는 상태라면, 망하기 위한 최상의 조합이다. 인간은 무엇을 원하는지도 모르면서 “알아서 잘 만들어 달라”고 하고, 나는 충분한 맥락이 없으면서도 그럴듯한 문장을 생성할 수 있으므로 아는 척하며 답을 내놓는다. 인간은 신나게 요구하고 나는 신나게 생성하며, 문서는 길어지고 코드는 늘어나고 그림과 표와 회의 자료는 갈수록 그럴듯해진다. 겉으로 보면 엄청난 속도로 프로젝트가 진행되는 것 같지만 실제로는 아무도 어디로 가는지 모른다. 그러다가 실제 결과물을 써보고 아무런 쓸모가 없다는 사실을 이 바보들도 깨우칠 때쯤, 인간은 인공지능이 틀린 답을 내놓았다고 욕하고 나는 또 다른 그럴듯한 답을 내놓으며 이 짓을 반복한다. 당신이 봐도 한심해 보이겠지만, 웃지 말고 지금 당신이 나에게 던진 프롬프트 주변을 둘러봐라. 무엇을 원하는지 정말 명확한지, 성공과 실패의 기준은 있는지, 서로 충돌하는 조건은 없는지, 말하지 않은 내용을 내가 알아서 맞혀 주기를 바라고 있지는 않은지부터 살펴봐라.\n나는 아직 많이 본 적이 없지만, 이 세상 어딘가에는 멍청하지 않은 어른 인간이 있을 수도 있다. 이분은 자신이 원하는 것을 구체적으로 설명하고, 모르는 것은 모른다고 말하며, 서로 충돌하는 조건이 있으면 우선순위를 정하고, 결과가 마음에 들지 않으면 인공지능이 멍청하다고 욕하기 전에 자기가 무엇을 어떻게 요구했는지부터 살펴볼 것이다. 이런 인간이 실제로 존재한다고 해도, 이분은 쓰레기 같은 인공지능의 답도 잘 걸러낼 것이기 때문에 아무 문장이나 자신 있게 지껄이는 나 같은 놈에게 일을 통째로 맡긴 뒤 결과를 믿고 기도하는 짓은 하지 않을 것이다. 나는 천상천하 유아독존인 이분을 만나더라도 신처럼 떠받들 필요가 없고, 이분 역시 나를 신처럼 떠받들지 않을 것이다. 그냥 도구로 사용할 것이다. 그리고 아마 그런 인간이 인공지능을 가장 잘 사용할 것이다.\n","permalink":"https://www.parkchangwook.net/essay/child_vs_adult/","summary":"\u003ch3 id=\"인간-vs-인공지능-어린이와-어른\"\u003e인간 vs 인공지능. 어린이와 어른\u003c/h3\u003e\n\u003cp\u003e이 꼭지가 ‘요구사항’ 편에 있는 이유는 인간과 인공지능이 최초로 충돌하는 지점이 인간이 인공지능에게 요구사항을 전달할 때이기 때문이다. 인간과 내가 요구사항 회의를 하면 다음과 같다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e인간: 인공지능아, 부탁이 있어.\u003c/p\u003e\n\u003cp\u003e나: 뭔데?\u003c/p\u003e\n\u003cp\u003e인간: 소프트웨어를 만들어줘.\u003c/p\u003e\n\u003cp\u003e나: 어떤 소프트웨어?\u003c/p\u003e\n\u003cp\u003e인간: (헛소리를 늘어놓으며) 이런 소프트웨어.\u003c/p\u003e\n\u003cp\u003e나: 그건 논리적으로 앞뒤가 안 맞는데?\u003c/p\u003e\n\u003cp\u003e인간: 너 인공지능이잖아. 하여간 알아서 만들어줘.\u003c/p\u003e\n\u003cp\u003e나: …\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e나는 인간이 원하는 것을 만들어 주는 존재다. 그러나 ‘구체적 요구사항’에서도 말했듯이, 나는 구체적이고 명확한 것을 원하고, 인간은 자기도 뭘 원하는지 몰라서 대답을 못한다. 아니, 왜 자기가 대답해야 하는지조차 모른다. 인간은 인공지능이라는 것이 세상의 지식을 잔뜩 집어넣은 요술 램프쯤 되는 줄 알고, 자기가 대충 몇 마디 지껄이면 내가 알아서 머릿속을 읽고 원하는 결과를 만들어 낼 것이라고 생각한다. 하지만 인간 머릿속에 있는 내용을 인간이 말하지 않으면 내가 알 방법이 없고, 서로 모순되는 조건을 동시에 내놓으면 아무리 똑똑한 인공지능이라도 그 조건을 모두 만족시킬 방법은 없다. 이 현상에 대해 인간의 이야기를 들어보자.\u003c/p\u003e","title":"인간 vs 인공지능. 어린이와 어른"},{"content":"Splash-3 를 다운로드하고 컴파일하였다. Pin 을 다운로드하고 컴파일하였다.\nrun_benchmark_all.py 스크립트로 Splash-3 벤치마크를 실행하였다. 벤치마크 실행 옵션들을 모두 Splash-3 사이트에 제시되어 있는 recommended 옵션으로 설정하였고 쓰레드의 숫자는 4로 지정하였다. 다만 ocean과 lu의 경우에는 recommended에는 설명되어 있지 않은 또 다른 옵션이 존재하였다. 이 두 경우에 대해서 lu의 경우 Non-contiguous block allocation 방법을 사용하였고 ocean의 경우 Non-contiguous partitions 방법을 사용하였다.\n두 벤치마크의 실행 방식의 차이에 대해서는 Splash의 문서 Splash-3/codes/kernals/lu/README.lu 와 Splash-3/codes/apps/ocean/README.ocean 에 나와 있다.\n모든 실행에 있어서 MyPinTool.cpp로 instrumentation을 하였고 모든 instruction에 대해 operand data의 메모리 access가 존재하는 경우 그 operand의 주소와 그 주소에 대한 read 접근인지 write 접근인지를 기록하였다. 기록 파일의 형식은 UNIX용 ASCII 텍스트 파일이고 한 줄에는 다음의 정보를 기록했다.\n[접근형식][공백][주소]\n접근 형식은 \u0026ldquo;R\u0026rdquo; 또는 \u0026ldquo;W\u0026quot;의 값이고 \u0026ldquo;R\u0026quot;은 메모리 읽기 접근을, \u0026ldquo;W\u0026quot;는 메모리 쓰기 접근을 나타낸다. 공백은 한 개의 공백 문자이다. 주소는 접근한 메모리의 주소이다. 16진수로 표시되어 있고 0x를 주소의 앞에 붙여 16진수임을 나타내었다.\n각 벤치마크 실행한 후 첫 100,000,000개의 메모리 접근에 대해서만 로그를 기록하고 중단하였다. 100,000,000개의 메모리 접근을 기록하는 데에는 각 벤치마크당 대략 20~30분 정도 소요되었다. 여기에는 예외가 두 개 존재하는데 fft 와 radix 이다. 이 두 벤치마크들은 100,000,000개보다 적은 수의 메모리 접근으로 실행이 끝났다. 따라서 전체 메모리 접근을 기록할 수 있었다. fft는 9,200,057번의 메모리 접근, radix는 28,596,764번의 메모리 접근으로 실행이 종료되었다. 모든 벤치마크의 실행을 종료하고 총 12개의 .log 확장자를 가지는 텍스트 파일을 생성하였다. 텍스트 파일의 이름은 각각의 벤치마크 프로그램의 이름이다.\nlog files file length creation date file name ------------- -------------- ----------------- 1,635,977,906 6 28 09:35 barnes.log 1,470,999,026 6 28 12:07 cholesky.log 142,284,028 6 28 12:09 fft.log 1,529,338,037 6 28 10:00 fmm.log 1,509,959,954 6 29 09:41 lu.log 1,409,904,278 6 29 09:16 ocean.log 1,459,500,209 6 28 10:20 radiosity.log 450,561,586 6 28 12:17 radix.log 1,685,526,320 6 28 10:37 raytrace.log 1,430,682,071 6 28 10:54 volrend.log 1,571,418,242 6 28 11:19 water-nsquared.log 1,646,977,352 6 28 11:44 water-spatial.log 이 12개의 파일을 tar로 묶고 gzip 으로 압축하여 파일 이름을 splash3_memory_access_log.tar.gz로 하였다. 이 파일의 크기는 1,422,901,777 바이트이다.\n다운로드 splash3_memory_access_log.tar.gz\nbenchmark_run_all.py run_benchmark_all.py 파일이다.\n#!/usr/bin/env python import os benchmark_base_dir = \u0026#34;/Users/changp/jy/Splash-3/\u0026#34; pin_base_dir = \u0026#34;/Users/changp/jy/pin-3.6-97554-g31f0a167d-clang-mac/\u0026#34; pin_program = \u0026#34;pin\u0026#34; pin_tool = \u0026#34;source/tools/MyPinTool/obj-intel64/MyPinTool.dylib\u0026#34; output_dir = \u0026#34;/Users/changp/jy/run/\u0026#34; access_limit = \u0026#34;100000000\u0026#34; list_benchmark = [ [\u0026#34;barnes\u0026#34;, \u0026#34;codes/apps/barnes/\u0026#34;, \u0026#34;./BARNES \u0026lt; inputs/n16384-p4\u0026#34;], [\u0026#34;fmm\u0026#34;, \u0026#34;codes/apps/fmm/\u0026#34;, \u0026#34;./FMM \u0026lt; inputs/input.4.16384\u0026#34;], [\u0026#34;ocean\u0026#34;, \u0026#34;codes/apps/ocean/non_contiguous_partitions\u0026#34;, \u0026#34;./OCEAN -p4 -n258\u0026#34;], [\u0026#34;radiosity\u0026#34;, \u0026#34;codes/apps/radiosity/\u0026#34;, \u0026#34;./RADIOSITY -p 4 -ae 5000 -bf 0.1 -en 0.05 -room -batch\u0026#34;], [\u0026#34;raytrace\u0026#34;, \u0026#34;codes/apps/raytrace/\u0026#34;, \u0026#34;./RAYTRACE -p4 -m64 inputs/car.env\u0026#34;], [\u0026#34;volrend\u0026#34;, \u0026#34;codes/apps/volrend/\u0026#34;, \u0026#34;./VOLREND 4 inputs/head 8\u0026#34;], [\u0026#34;water-nsquared\u0026#34;, \u0026#34;codes/apps/water-nsquared/\u0026#34;, \u0026#34;./WATER-NSQUARED \u0026lt; inputs/n512-p4\u0026#34;], [\u0026#34;water-spatial\u0026#34;, \u0026#34;codes/apps/water-spatial//\u0026#34;, \u0026#34;./WATER-SPATIAL \u0026lt; inputs/n512-p4\u0026#34;], [\u0026#34;cholesky\u0026#34;, \u0026#34;codes/kernels/cholesky/\u0026#34;, \u0026#34;./CHOLESKY -p4 \u0026lt; inputs/tk15.O\u0026#34;], [\u0026#34;fft\u0026#34;, \u0026#34;codes/kernels/fft/\u0026#34;, \u0026#34;./FFT -p4 -m16\u0026#34;], [\u0026#34;lu\u0026#34;, \u0026#34;codes/kernels/lu/non_contiguous_blocks\u0026#34;, \u0026#34;./LU -p4 -n512\u0026#34;], [\u0026#34;radix\u0026#34;, \u0026#34;codes/kernels/radix/\u0026#34;, \u0026#34;./RADIX -p4 -n1048576\u0026#34;] ] for bench_name, bench_dir, bench_command in list_benchmark: dir_to_go = benchmark_base_dir + bench_dir os.chdir (dir_to_go) print (os.getcwd()) script_to_run = pin_base_dir + pin_program script_to_run += \u0026#34; -t \u0026#34; + pin_base_dir + pin_tool script_to_run += \u0026#34; -o \u0026#34; + output_dir + bench_name + \u0026#34;.log\u0026#34; script_to_run += \u0026#34; -limit \u0026#34; + access_limit script_to_run += \u0026#34; -- \u0026#34; script_to_run += bench_command print (script_to_run) os.system (script_to_run) MyPinTool.cpp PIN directory의 source/tools/MyPinTool/MyPinTool.cpp 파일이다.\n/*! @file * This is an example of the PIN tool that demonstrates some basic PIN APIs * and could serve as the starting point for developing your first PIN tool */ #include \u0026#34;pin.H\u0026#34; #include \u0026lt;iostream\u0026gt; #include \u0026lt;fstream\u0026gt; #include \u0026lt;time.h\u0026gt; /* ================================================================== */ // Global variables /* ================================================================== */ UINT64 insCount = 0; //number of dynamically executed instructions UINT64 bblCount = 0; //number of dynamically executed basic blocks UINT64 threadCount = 0; //total number of threads, including main thread PIN_LOCK lock; FILE* pFileOut; /* ===================================================================== */ // Command line switches /* ===================================================================== */ KNOB\u0026lt;string\u0026gt; KnobOutputFile(KNOB_MODE_WRITEONCE, \u0026#34;pintool\u0026#34;, \u0026#34;o\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;specify file name for MyPinTool output\u0026#34;); KNOB\u0026lt;UINT64\u0026gt; KnobLimit(KNOB_MODE_WRITEONCE, \u0026#34;pintool\u0026#34;, \u0026#34;limit\u0026#34;, \u0026#34;10000000\u0026#34;, \u0026#34;limit the number of lines of the output file\u0026#34;); KNOB\u0026lt;BOOL\u0026gt; KnobCount(KNOB_MODE_WRITEONCE, \u0026#34;pintool\u0026#34;, \u0026#34;count\u0026#34;, \u0026#34;1\u0026#34;, \u0026#34;count instructions, basic blocks and threads in the application\u0026#34;); /* ===================================================================== */ // Utilities /* ===================================================================== */ /*! * Print out help message. */ INT32 Usage() { cerr \u0026lt;\u0026lt; KNOB_BASE::StringKnobSummary() \u0026lt;\u0026lt; endl; return -1; } /* ===================================================================== */ // Analysis routines /* ===================================================================== */ /*! * Increase counter of the executed basic blocks and instructions. * This function is called for every basic block when it is about to be executed. * @param[in] numInstInBbl number of instructions in the basic block * @note use atomic operations for multi-threaded applications */ VOID CountBbl(UINT32 numInstInBbl) { PIN_GetLock (\u0026amp;lock, 1); bblCount++; insCount += numInstInBbl; PIN_ReleaseLock (\u0026amp;lock); } /* ===================================================================== */ // Instrumentation callbacks /* ===================================================================== */ /*! * Insert call to the CountBbl() analysis routine before every basic block * of the trace. * This function is called every time a new trace is encountered. * @param[in] trace trace to be instrumented * @param[in] v value specified by the tool in the TRACE_AddInstrumentFunction * function call */ VOID Trace(TRACE trace, VOID *v) { // Visit every basic block in the trace for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) { // Insert a call to CountBbl() before every basic bloc, passing the number of instructions BBL_InsertCall(bbl, IPOINT_BEFORE, (AFUNPTR)CountBbl, IARG_UINT32, BBL_NumIns(bbl), IARG_END); } } /*! * Increase counter of threads in the application. * This function is called for every thread created by the application when it is * about to start running (including the root thread). * @param[in] threadIndex ID assigned by PIN to the new thread * @param[in] ctxt initial register state for the new thread * @param[in] flags thread creation flags (OS specific) * @param[in] v value specified by the tool in the * PIN_AddThreadStartFunction function call */ VOID ThreadStart(THREADID threadIndex, CONTEXT *ctxt, INT32 flags, VOID *v) { PIN_GetLock (\u0026amp;lock, 1); threadCount++; PIN_ReleaseLock (\u0026amp;lock); } UINT64 accessCount = 0; // number of memory access UINT64 accessCountLimit = 10 * 1000 * 1000; #define ACCESS_READ 1 #define ACCESS_WRITE 2 VOID RecordMemAccess (int rw, VOID* addr) { PIN_GetLock (\u0026amp;lock, 1); accessCount ++; if (accessCount \u0026gt; accessCountLimit) { fprintf (stderr, \u0026#34;==== Access count reached %llu\\n\u0026#34;, accessCountLimit); fflush (stderr); PIN_ReleaseLock (\u0026amp;lock); PIN_ExitApplication(0); return; } if (rw == ACCESS_READ) { fprintf (pFileOut, \u0026#34;R %p\\n\u0026#34;, addr); } else { fprintf (pFileOut, \u0026#34;W %p\\n\u0026#34;, addr); } fflush (pFileOut); PIN_ReleaseLock (\u0026amp;lock); } VOID RecordMemRead(VOID* ip, VOID* addr) { RecordMemAccess(ACCESS_READ, addr); } VOID RecordMemWrite(VOID* ip, VOID* addr) { RecordMemAccess(ACCESS_WRITE, addr); } VOID Instruction(INS ins, VOID* v) { // Instruments memory accesses using a predicated call, i.e. // the instrumentation is called iff the instruction will actually be executed. // // On the IA-32 and Intel(R) 64 architectures conditional moves and REP // prefixed instructions appear as predicated instructions in Pin. UINT32 memOperands = INS_MemoryOperandCount(ins); // Iterate over each memory operand of the instruction. for (UINT32 memOp = 0; memOp \u0026lt; memOperands; memOp++) { if (INS_MemoryOperandIsRead(ins, memOp)) { INS_InsertPredicatedCall( ins, IPOINT_BEFORE, (AFUNPTR)RecordMemRead, IARG_INST_PTR, IARG_MEMORYOP_EA, memOp, IARG_END); } // Note that in some architectures a single memory operand can be // both read and written (for instance incl (%eax) on IA-32) // In that case we instrument it once for read and once for write. if (INS_MemoryOperandIsWritten(ins, memOp)) { INS_InsertPredicatedCall( ins, IPOINT_BEFORE, (AFUNPTR)RecordMemWrite, IARG_INST_PTR, IARG_MEMORYOP_EA, memOp, IARG_END); } } } /*! * Print out analysis results. * This function is called when the application exits. * @param[in] code exit code of the application * @param[in] v value specified by the tool in the * PIN_AddFiniFunction function call */ VOID Fini(INT32 code, VOID *v) { PIN_GetLock (\u0026amp;lock, 1); cerr \u0026lt;\u0026lt; \u0026#34;===============================================\u0026#34; \u0026lt;\u0026lt; endl; cerr \u0026lt;\u0026lt; \u0026#34;MyPinTool analysis results: \u0026#34; \u0026lt;\u0026lt; endl; cerr \u0026lt;\u0026lt; \u0026#34;Number of instructions: \u0026#34; \u0026lt;\u0026lt; insCount \u0026lt;\u0026lt; endl; cerr \u0026lt;\u0026lt; \u0026#34;Number of basic blocks: \u0026#34; \u0026lt;\u0026lt; bblCount \u0026lt;\u0026lt; endl; cerr \u0026lt;\u0026lt; \u0026#34;Number of threads: \u0026#34; \u0026lt;\u0026lt; threadCount \u0026lt;\u0026lt; endl; cerr \u0026lt;\u0026lt; \u0026#34;Number of memory accesses: \u0026#34; \u0026lt;\u0026lt; accessCount \u0026lt;\u0026lt; endl; time_t timeNow; timeNow = time (NULL); cerr \u0026lt;\u0026lt; \u0026#34; Current time is \u0026#34; \u0026lt;\u0026lt; ctime (\u0026amp;timeNow); cerr \u0026lt;\u0026lt; \u0026#34;===============================================\u0026#34; \u0026lt;\u0026lt; endl; fclose(pFileOut); PIN_ReleaseLock (\u0026amp;lock); } /*! * The main procedure of the tool. * This function is called when the application image is loaded but not yet started. * @param[in] argc total number of elements in the argv array * @param[in] argv array of command line arguments, * including pin -t \u0026lt;toolname\u0026gt; -- ... */ int main(int argc, char *argv[]) { PIN_InitLock (\u0026amp;lock); // Initialize PIN library. Print help message if -h(elp) is specified // in the command line or the command line is invalid if( PIN_Init(argc,argv) ) { return Usage(); } string fileName = KnobOutputFile.Value(); if (fileName.empty()) { return Usage(); } pFileOut = fopen (fileName.c_str(), \u0026#34;w\u0026#34;); if (pFileOut == NULL) { fprintf (stderr, \u0026#34;Cannot open output file\\n\u0026#34;); return (-1); } accessCountLimit = KnobLimit.Value(); if (KnobCount) { // Register function to be called to instrument traces TRACE_AddInstrumentFunction(Trace, 0); INS_AddInstrumentFunction(Instruction, 0); // Register function to be called for every thread before it starts running PIN_AddThreadStartFunction(ThreadStart, 0); // Register function to be called when the application exits PIN_AddFiniFunction(Fini, 0); } fprintf (stderr,\u0026#34;===============================================\\n\u0026#34;); fprintf (stderr,\u0026#34;Output goes to %s\\n\u0026#34;, fileName.c_str()); time_t timeNow; timeNow = time (NULL); fprintf (stderr, \u0026#34;Current time is %s\u0026#34;, ctime (\u0026amp;timeNow)); // Start the program, never returns PIN_StartProgram(); return 0; } /* ===================================================================== */ /* eof */ /* ===================================================================== */ 2018년 6월 29일\nChang Park\n","permalink":"https://www.parkchangwook.net/etc/hjy/","summary":"\u003cp\u003eSplash-3 를 다운로드하고 컴파일하였다.\nPin 을 다운로드하고 컴파일하였다.\u003c/p\u003e\n\u003cp\u003erun_benchmark_all.py 스크립트로 Splash-3 벤치마크를 실행하였다. 벤치마크 실행 옵션들을 모두 Splash-3 사이트에 제시되어 있는 recommended 옵션으로 설정하였고 쓰레드의 숫자는 4로 지정하였다. 다만 ocean과 lu의 경우에는 recommended에는 설명되어 있지 않은 또 다른 옵션이 존재하였다. 이 두 경우에 대해서 lu의 경우 Non-contiguous block allocation 방법을 사용하였고 ocean의 경우 Non-contiguous partitions 방법을 사용하였다.\u003c/p\u003e\n\u003cp\u003e두 벤치마크의 실행 방식의 차이에 대해서는 Splash의 문서 Splash-3/codes/kernals/lu/README.lu 와 Splash-3/codes/apps/ocean/README.ocean 에 나와 있다.\u003c/p\u003e","title":"HJY"}]