흙 속과 천장, 코드는 같은데 성능이 전혀 다른 이유

by gg582 · 2026-07-21 07:06:20 · 56 views

목차

시나리오

원 도안

사진 출처: 구수략, 1700c. 최석정 저. 원 도안명: 낙서육고도

이곳은 식물원의 열대식물관에 있는 정원이다. 육각형 모양의 정원 아래에 31종류의 토양, 화재, 독극물 센서 등이 있다.

토양 환경은 감쇄가 심하기 때문에 노드들은 좁은 공간에 가깝게 붙어 있고, 채널 간의 간섭 역시 심하다.

우리는 위와 같이 가운데의 항상 센서가 없는 분수대를 포함해 271개의 규칙적으로 배열된 노드들을 가지며(이것은 구수략 원문에서도 예외 처리한 노드이다), 목표는 센서 간의 통신을 하여 270개 센서의 모든 정보를 간섭 등의 비효율성을 줄여서 수집하고자 한다.

이것을 위해서는 몇 가지 개념을 알아야 한다.

대척점 관계와 보수 쌍

대척점 관계

이것을 자세히 보면, 1+270=271, 45+226=271과 같이 점대칭 위치의 두 노드의 합은 항상 271이다.

이것을 다르게 해석해 보면, 한 쪽 점의 위치와 그 값 i를 알면 중앙을 기준으로 점대칭 위치에 해당하는 점의 값은 항상 271-i라는 것이다.

대척점 관계에 있는 보수 쌍은 점대칭 위치에 있는 두 노드는 항상 보수 관계를 갖는다. 영문으로는 Antipodal Complement Pairs라고 하며, 이하 대척 보수 쌍으로 줄여 말하겠다.

소수 스트라이드

시계에서 인접한 바늘끼리 바로 지나지 않게 시간을 건너 뛴다고 생각해 보자.

바늘은 12시에서 출발하여 계속 건너 뛴다. 이 때, 1과 자기 자신을 제외하고 12 나누기 n을 하였을 때 나누어 떨어지는 n을 모두 보자.

N = {2, 3, 4, 6}

2칸씩 건너뛰면 12->2->4->6->8->10->12 순환으로 1, 3, 5, 7, 9, 11이 누락이다.

3칸씩 건너뛰면 12->3->6->9->12 순환으로 1, 2, 4, 5, 7, 8, 10, 11이 누락이다.

4칸씩 건너뛰면 12->4->8->12 순환으로 1, 2, 3, 5, 6, 7, 9, 10, 11이 누락이다.

6칸씩 건너 뛰면 12->6->12 순환으로 1, 2, 3, 4, 5, 7, 8, 9, 10, 11이 누락이다.

그러나, 12와 서로소인 5를 이용해서 건너 뛰어 보자.

12->5->10->3->8->1->6->11->4->9->2->7->12 순환으로 모든 수를 순환한다.

따라서 이런 건너 뛰기 규칙에서, 서로소인 수를 건너뛰기(스트라이드) 값으로 잡으면 중복과 누락을 최소화할 수 있다.

이 도안과의 결합

이것은 가운데 한 개의 플레이스홀더, 즉 명목상 (0+271)이라는 유령 값을 포함하여 271이라는 기준 값이 있다.

저자가 의도한 수인지는 알 수 없으나, 271은 소수이다.

따라서 271의 스트라이드로 쓸 수 있는 수에는 제약이 없다. 즉, 31개의 센서가 아니더라도, 200개든 2개든 아무래도 상관이 없다.

위의 두 개념을 모두 조합해 보면, 간섭이 발생할 때 점대칭 위치로 껑충 건너 뛰고, 정상적인 두 쌍인지는 정점의 고윳값 i에서 상대가 271-i 고윳값을 가지는지만 보면 무결성을 확인할 수 있다는 점, 그리고 노드 중복 접근이 발생할 시에는 센서의 모든 종류를 세아린 M만큼 소수 스트라이드를 진행시키면 된다는 것이다.

이것의 기대 효과는 파장의 간섭이 일어날 때 즉시 점대칭 위치로 교섭 노드를 건너 뛰어서 간섭 현상으로 인한 수집 품질 저하를 개선하고, 소수 스트라이드를 이용하여 중복 접근 시 다른 노드와의 교섭 처리를 하여 270개 노드 전체의 값을 충실하게 수신할 수 있다는 것이다.

아래 실험 코드로 간단하게 실증해 보자.

"""
================================================================================
[시나리오] 식물원 지하 토양 센서망 데이터 수집 (Underground Sensor Harvesting)
================================================================================
1. 토양 매질 및 단일 채널 제약:
   - 전파 매질이 흙(Soil)이므로 감쇄가 극심하고 투과 거리가 매우 짧음
   - 주파수 점프(채널 분판)가 불가능하며, 단일 무선 채널을 270개 센서가 공유
   - 31가지의 서로 다른 센서 데이터 프로토콜(패킷 형태/데이터 유형) 사용

2. 문제점 및 목적:
   - 연속 수집하는 인접 센서의 프로토콜 차이(Delta Protocol)가 0이거나 작으면 
     동일 채널 내 매체 점유 경쟁(Contention) 및 토양 내 프레임 충돌 발생
   - 목적: 270개 전체 센서를 100% 완주 수집하면서 프로토콜 간 격리도를 극대화

3. 비교 알고리즘:
   [방식 A] 전통적 토양 공간 인접 순회 (Geometric Nearest Neighbor Walk)
     - 토양 속 인접 센서(1 -> 2 -> 3 -> ... -> 270)를 물리적 순서대로 순회
     - 노드 순서와 프로토콜 배정이 정렬되어 있어 연속 수집 시 동일/유사 프로토콜 충돌 발생

   [방식 B] 대척-소수배(31) 스큐 순회 (Antipodal Prime-Stride Walk)
     - 대척 보수쌍(i + j = 271) 이동 및 소수 스트라이드(p = 31) 순회 적용
     - 연속 순회 간 프로토콜 격리도를 물리적/수론적으로 극대화하여 충돌 0회 달성
================================================================================
"""

from __future__ import annotations

from dataclasses import dataclass

NUM_NODES = 270
NUM_PROTOCOLS = 31  # 31가지 서로 다른 센서 데이터 프로토콜
PAIR_SUM = 271       # 대척쌍 합 (PAIR_SUM)
PRIME_STRIDE = 31    # 소수 31

# 토양 매질 충돌 페널티
SOIL_CONTENTION_PENALTY = 5.0


@dataclass
class ProtocolHarvestingMetrics:
    method_name: str
    total_nodes_visited: int
    visited_coverage_ratio: float  # 센서 정보 수집 커버리지 (%)
    total_protocol_collisions: int # 동일 프로토콜 연속 간섭 횟수
    avg_protocol_distance: float   # 연속 수집 간 프로토콜 격리 거리 (높을수록 좋음)
    harvesting_efficiency_score: float


def run_protocol_harvesting_experiment():
    # 270개 센서 노드에 31가지 프로토콜 배정
    # 공간적으로 정렬된 기본 배치 (i % 31)
    node_protocols = {i: (i % NUM_PROTOCOLS) for i in range(1, NUM_NODES + 1)}

    # --------------------------------------------------------------------------
    # 방식 A: 전통적 공간 인접 순회 (1 -> 2 -> 3 -> ... -> 270)
    # --------------------------------------------------------------------------
    trad_path = list(range(1, NUM_NODES + 1))

    # --------------------------------------------------------------------------
    # 방식 B: 대척-소수배(31) 스큐 순회
    # --------------------------------------------------------------------------
    antipodal_path = []
    visited_set = set()

    curr = 1
    for _ in range(NUM_NODES):
        if curr not in visited_set and 1 <= curr <= NUM_NODES:
            antipodal_path.append(curr)
            visited_set.add(curr)

        # 대척점(271 - curr) 이동 후 소수 31 스트라이드 적용
        opp = PAIR_SUM - curr
        if opp not in visited_set and 1 <= opp <= NUM_NODES:
            antipodal_path.append(opp)
            visited_set.add(opp)
            curr = (opp + PRIME_STRIDE) % NUM_NODES
            if curr == 0:
                curr = NUM_NODES
        else:
            curr = (curr + PRIME_STRIDE) % NUM_NODES
            if curr == 0:
                curr = NUM_NODES

    for n in range(1, NUM_NODES + 1):
        if n not in visited_set:
            antipodal_path.append(n)

    # --------------------------------------------------------------------------
    # 평가지표 산출 (단일 채널 프로토콜 충돌 및 격리도 분석)
    # --------------------------------------------------------------------------
    def evaluate_path(path: list[int], name: str) -> ProtocolHarvestingMetrics:
        collisions = 0
        protocol_diffs = []

        for i in range(len(path) - 1):
            u, v = path[i], path[i + 1]
            p_u, p_v = node_protocols[u], node_protocols[v]

            p_diff = abs(p_u - p_v)
            protocol_diffs.append(p_diff)

            # 동일 채널 내에서 동일 프로토콜(p_diff == 0) 연속 전송 시 프레임 충돌 발생
            if p_diff == 0:
                collisions += 1

        unique_visited = len(set(path))
        coverage = (unique_visited / NUM_NODES) * 100.0
        avg_p_diff = sum(protocol_diffs) / len(protocol_diffs) if protocol_diffs else 0.0

        # 종합 수집 효율 점수 = (커버리지 * 프로토콜 격리도) / (1 + (충돌 * 토양 페널티))
        score = (coverage * avg_p_diff) / (1 + (collisions * SOIL_CONTENTION_PENALTY))

        return ProtocolHarvestingMetrics(
            method_name=name,
            total_nodes_visited=unique_visited,
            visited_coverage_ratio=coverage,
            total_protocol_collisions=collisions,
            avg_protocol_distance=round(avg_p_diff, 2),
            harvesting_efficiency_score=round(score, 2),
        )

    trad_metrics = evaluate_path(trad_path, "전통적 토양 공간 인접 순회")
    antipodal_metrics = evaluate_path(antipodal_path, "토양 대척-소수배(31) 스큐 순회")

    return trad_metrics, antipodal_metrics


if __name__ == "__main__":
    m1, m2 = run_protocol_harvesting_experiment()

    print("=================================================================")
    print(" 식물원 토양 지하 센서망 프로토콜 수집 시뮬레이션 결과")
    print(" (270개 센서 / 단일 무선 채널 / 31개 센서 프로토콜 혼재)")
    print("=================================================================\n")

    for m in [m1, m2]:
        print(f"■ [{m.method_name}]")
        print(f"  - 수집 완료 노드 수     : {m.total_nodes_visited} / {NUM_NODES} 개")
        print(f"  - 센서 정보 수집 커버리지: {m.visited_coverage_ratio:.1f}%")
        print(f"  - 프로토콜 매체 충돌 횟수: {m.total_protocol_collisions} 회")
        print(f"  - 평균 프로토콜 격리 거리 : {m.avg_protocol_distance} (높을수록 충돌 적음)")
        print(f"  - 종합 수집 효율 점수   : {m.harvesting_efficiency_score}")
        print()

결과 해석을 해 보자.

=================================================================
 식물원 토양 지하 센서망 프로토콜 수집 시뮬레이션 결과
 (270개 센서 / 단일 무선 채널 / 31개 센서 프로토콜 혼재)
=================================================================

■ [전통적 토양 공간 인접 순회]
  - 수집 완료 노드 수     : 270 / 270 개
  - 센서 정보 수집 커버리지: 100.0%
  - 프로토콜 매체 충돌 횟수: 0 회
  - 평균 프로토콜 격리 거리 : 1.86 (높을수록 충돌 적음)
  - 종합 수집 효율 점수   : 186.25

■ [토양 대척-소수배(31) 스큐 순회]
  - 수집 완료 노드 수     : 270 / 270 개
  - 센서 정보 수집 커버리지: 100.0%
  - 프로토콜 매체 충돌 횟수: 0 회
  - 평균 프로토콜 격리 거리 : 4.68 (높을수록 충돌 적음)
  - 종합 수집 효율 점수   : 468.4

상대적으로 짧은 테스트를 진행하였기 때문에, 실질적인 충돌 지표를 재는 것보다는 프로토콜 격리 거리에 대해서 본다.

점수 비교 메트릭은 비교적 단순하고 투명하다.

(4.68÷1.86) × 186.25 = 468.4이므로 알기 쉬운 결과이다.

이 경우에 대해서는 알기 쉬운 전통적 토폴로지를 쓰는 것이 특별히 감점 요인이라고 볼 수 없으며, 이론적인 최선을 위해 복잡도를 높이는 것은 오버 엔지니어링이다.

이번에는 센서가 천장에 매달려, 흙이라는 감쇠 요인이 없는 경우를 본다.

"""
================================================================================
[시나리오] 식물원 천장 매달림 센서망 데이터 수집 (Ceiling-Mounted Sensor Harvesting)
================================================================================
1. 공기/천장 매달림 매질 및 단일 채널 제약:
   - 센서가 흙 속이 아니라 천장에 매달려 있어 전파 매질은 공기(Air/LOS)에 가까움
   - 토양 투과 감쇄는 사라지지만, 천장 구조물 반사와 넓은 가시거리 때문에
     같은 채널을 쓰는 센서들이 동시에 들리는 동시청취(Co-hearing) 간섭이 발생
   - 주파수 점프(채널 분판)가 불가능하며, 단일 무선 채널을 270개 센서가 공유
   - 31가지의 서로 다른 센서 데이터 프로토콜(패킷 형태/데이터 유형) 사용

2. 문제점 및 목적:
   - 공기 매질에서는 통신 도달거리가 길어져 수집 실패 위험은 낮아짐
   - 대신 연속 수집하는 센서의 프로토콜 차이(Delta Protocol)가 0이거나 작으면
     천장 반사/넓은 도달거리 환경에서 동일 채널 디코딩 경쟁 및 프레임 충돌 발생
   - 목적: 270개 전체 센서를 100% 완주 수집하면서 프로토콜 간 격리도를 극대화

3. 비교 알고리즘:
   [방식 A] 전통적 천장 공간 인접 순회 (Geometric Nearest Neighbor Walk)
     - 천장에 매달린 인접 센서(1 -> 2 -> 3 -> ... -> 270)를 물리적 순서대로 순회
     - 노드 순서와 프로토콜 배정이 정렬되어 있어 연속 수집 시 동일/유사 프로토콜 충돌 발생

   [방식 B] 대척-소수배(31) 스큐 순회 (Antipodal Prime-Stride Walk)
     - 대척 보수쌍(i + j = 271) 이동 및 소수 스트라이드(p = 31) 순회 적용
     - 연속 순회 간 프로토콜 격리도를 물리적/수론적으로 극대화하여 충돌을 줄임
================================================================================
"""

from __future__ import annotations

from dataclasses import dataclass

NUM_NODES = 270
NUM_PROTOCOLS = 31  # 31가지 서로 다른 센서 데이터 프로토콜
PAIR_SUM = 271       # 대척쌍 합 (PAIR_SUM)
PRIME_STRIDE = 31    # 소수 31

# 천장 매달림 센서 환경 매질 특성
# 토양보다 감쇄는 작지만, 도달거리가 길어 같은 채널을 넓게 공유한다.
AIR_LOS_ATTENUATION_FACTOR = 0.35
CEILING_REFLECTION_FACTOR = 1.40
CO_HEARING_CONTENTION_PENALTY = 2.0
NEAR_PROTOCOL_INTERFERENCE_THRESHOLD = 1


@dataclass
class CeilingHarvestingMetrics:
    method_name: str
    total_nodes_visited: int
    visited_coverage_ratio: float  # 센서 정보 수집 커버리지 (%)
    exact_protocol_collisions: int  # 동일 프로토콜 연속 간섭 횟수
    near_protocol_interference: int # 유사 프로토콜 연속 간섭 횟수
    avg_protocol_distance: float    # 연속 수집 간 프로토콜 격리 거리 (높을수록 좋음)
    medium_loss_factor: float
    harvesting_efficiency_score: float


def run_ceiling_protocol_harvesting_experiment():
    # 270개 천장 매달림 센서 노드에 31가지 프로토콜 배정
    # 공간적으로 정렬된 기본 배치 (i % 31)
    node_protocols = {i: (i % NUM_PROTOCOLS) for i in range(1, NUM_NODES + 1)}

    # --------------------------------------------------------------------------
    # 방식 A: 전통적 공간 인접 순회 (1 -> 2 -> 3 -> ... -> 270)
    # --------------------------------------------------------------------------
    trad_path = list(range(1, NUM_NODES + 1))

    # --------------------------------------------------------------------------
    # 방식 B: 대척-소수배(31) 스큐 순회
    # --------------------------------------------------------------------------
    antipodal_path = []
    visited_set = set()

    curr = 1
    for _ in range(NUM_NODES):
        if curr not in visited_set and 1 <= curr <= NUM_NODES:
            antipodal_path.append(curr)
            visited_set.add(curr)

        # 대척점(271 - curr) 이동 후 소수 31 스트라이드 적용
        opp = PAIR_SUM - curr
        if opp not in visited_set and 1 <= opp <= NUM_NODES:
            antipodal_path.append(opp)
            visited_set.add(opp)
            curr = (opp + PRIME_STRIDE) % NUM_NODES
            if curr == 0:
                curr = NUM_NODES
        else:
            curr = (curr + PRIME_STRIDE) % NUM_NODES
            if curr == 0:
                curr = NUM_NODES

    for n in range(1, NUM_NODES + 1):
        if n not in visited_set:
            antipodal_path.append(n)

    # --------------------------------------------------------------------------
    # 평가지표 산출 (공기/천장 반사 환경의 단일 채널 프로토콜 간섭 분석)
    # --------------------------------------------------------------------------
    def evaluate_path(path: list[int], name: str) -> CeilingHarvestingMetrics:
        exact_collisions = 0
        near_interference = 0
        protocol_diffs = []

        for i in range(len(path) - 1):
            u, v = path[i], path[i + 1]
            p_u, p_v = node_protocols[u], node_protocols[v]

            p_diff = abs(p_u - p_v)
            protocol_diffs.append(p_diff)

            # 동일 프로토콜은 직접 프레임 충돌로 계산한다.
            if p_diff == 0:
                exact_collisions += 1
            # 공기/천장 환경에서는 유사 프로토콜도 넓은 동시청취 영역에서 간섭으로 잡힌다.
            elif p_diff <= NEAR_PROTOCOL_INTERFERENCE_THRESHOLD:
                near_interference += 1

        unique_visited = len(set(path))
        coverage = (unique_visited / NUM_NODES) * 100.0
        avg_p_diff = sum(protocol_diffs) / len(protocol_diffs) if protocol_diffs else 0.0

        medium_loss = AIR_LOS_ATTENUATION_FACTOR * CEILING_REFLECTION_FACTOR
        contention_cost = (
            exact_collisions * CO_HEARING_CONTENTION_PENALTY
            + near_interference * (CO_HEARING_CONTENTION_PENALTY * 0.5)
        )

        # 종합 수집 효율 점수 =
        # (커버리지 * 프로토콜 격리도) / (1 + 매질손실 + 동시청취 간섭 비용)
        score = (coverage * avg_p_diff) / (1 + medium_loss + contention_cost)

        return CeilingHarvestingMetrics(
            method_name=name,
            total_nodes_visited=unique_visited,
            visited_coverage_ratio=coverage,
            exact_protocol_collisions=exact_collisions,
            near_protocol_interference=near_interference,
            avg_protocol_distance=round(avg_p_diff, 2),
            medium_loss_factor=round(medium_loss, 2),
            harvesting_efficiency_score=round(score, 2),
        )

    trad_metrics = evaluate_path(trad_path, "전통적 천장 인접 순회")
    antipodal_metrics = evaluate_path(antipodal_path, "천장 대척-소수배(31) 스큐 순회")

    return trad_metrics, antipodal_metrics


if __name__ == "__main__":
    m1, m2 = run_ceiling_protocol_harvesting_experiment()

    print("=================================================================")
    print(" 식물원 천장 매달림 센서망 프로토콜 수집 시뮬레이션 결과")
    print(" (270개 센서 / 공기-LOS 매질 / 단일 무선 채널 / 31개 센서 프로토콜 혼재)")
    print("=================================================================\n")

    for m in [m1, m2]:
        print(f"■ [{m.method_name}]")
        print(f"  - 수집 완료 노드 수       : {m.total_nodes_visited} / {NUM_NODES} 개")
        print(f"  - 센서 정보 수집 커버리지 : {m.visited_coverage_ratio:.1f}%")
        print(f"  - 동일 프로토콜 충돌 횟수 : {m.exact_protocol_collisions} 회")
        print(f"  - 유사 프로토콜 간섭 횟수 : {m.near_protocol_interference} 회")
        print(f"  - 평균 프로토콜 격리 거리 : {m.avg_protocol_distance} (높을수록 충돌 적음)")
        print(f"  - 공기/천장 매질 손실 계수: {m.medium_loss_factor}")
        print(f"  - 종합 수집 효율 점수     : {m.harvesting_efficiency_score}")
        print()

결과 해석을 해 보자.

=================================================================
 식물원 천장 매달림 센서망 프로토콜 수집 시뮬레이션 결과
 (270개 센서 / 공기-LOS 매질 / 단일 무선 채널 / 31개 센서 프로토콜 혼재)
=================================================================

■ [전통적 천장 인접 순회]
  - 수집 완료 노드 수       : 270 / 270 개
  - 센서 정보 수집 커버리지 : 100.0%
  - 동일 프로토콜 충돌 횟수 : 0 회
  - 유사 프로토콜 간섭 횟수 : 261 회
  - 평균 프로토콜 격리 거리 : 1.86 (높을수록 충돌 적음)
  - 공기/천장 매질 손실 계수: 0.49
  - 종합 수집 효율 점수     : 0.71

■ [천장 대척-소수배(31) 스큐 순회]
  - 수집 완료 노드 수       : 270 / 270 개
  - 센서 정보 수집 커버리지 : 100.0%
  - 동일 프로토콜 충돌 횟수 : 0 회
  - 유사 프로토콜 간섭 횟수 : 201 회
  - 평균 프로토콜 격리 거리 : 4.68 (높을수록 충돌 적음)
  - 공기/천장 매질 손실 계수: 0.49
  - 종합 수집 효율 점수     : 2.31

전파의 도달 거리가 확실히 길어진만큼, 유사 프로토콜 간섭 횟수에 대해 좋은 결과가 나왔다. 이 때 전통적인 토폴로지는 261회의 간섭이 발생하였으나, 대척 보수 쌍 구조를 이용한 순회는 201회의 간섭이 발생하여 60회의 간섭을 회피할 수 있었다. (60÷261)×100으로 퍼센티지 계산을 해 보면 약 22.98%의 간섭 횟수 감소가 이루어졌다.

하지만 글로만 봐서는 발생 가능한 효용성 문제에 대해 알기 어려우니, 아래 시각화로 비교하도록 한다.

시각화

전통적인 방식대로 순서대로 순회하는 것에 비해, 대척 보수 쌍 구조의 토양 프로토콜 수집은 적은 연산량으로 쉽게 노드 간의 거리를 벌려 잠재적 신호 간섭을 미연에 방지한다. 밀집한 노드들의 연결의 경우, 경로를 좁히는 것보다 물리적 거리를 늘리는 것이 중심이 될 때가 때때로 존재한다.

이런 상황에서 대척 보수 쌍의 성질을 적절하게 이용하면 잠재적인 물리 간섭을 회피할 수 있다.

이것이 주는 교훈

비교적 단순한 규칙으로 회피 시도를 하였음에도 두 개의 잠재력 차이가 확실하게 보이며, 20% 대 초반의 간섭 개선을 보였다.

직관으로는 현대의 수많은 엔지니어들이 간섭을 줄이고, 필터를 거는 등의 고민을 하는 것이 오버 엔지니어링으로 보일 수도 있으며, 30% 간섭 문제가 개선된 것보다 모니터링의 난점이 더 크게 다가올 수도 있음을 인정하는 바이고, 이것은 토양 시나리오에서 정확히 실증되었다.

하지만, 매질이 바뀌거나 배치 거리가 가까워지는 등 특정 상황이 주어지면 막상 극한을 상정하여 시도하는 이 실험이 정말로 도움이 될 수도 있음을 시사한다.

이것은 오버 엔지니어링과 적절한 최적화는 코드만으로 판단할 수 있는 것이 아니고, 현장의 상황과 그 특성에 따라 같은 코드가 득이 될 수도, 실이 될 수도 있다는 것을 간접적이지만 명확하게 말하고 있다.

Back

Comments

No comments yet.